Skip to content

ci: derive a job ceiling for lint, bundle-analysis and release (objectui#7270) - #7957

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-7270-job-timeouts-derived-per-job
Sep 6, 2026
Merged

ci: derive a job ceiling for lint, bundle-analysis and release (objectui#7270)#7957
baozhoutao merged 1 commit into
mainfrom
claude/issue-7270-job-timeouts-derived-per-job

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #7270

Three jobs declared no job-level timeout-minutes, so their only backstop was GitHub's documented 360-minute default. Two of them are required contexts. This PR gives each of the three a ceiling derived from that job's own measured run distribution, and records the derivation beside the key.

The card asked for exactly one ruling — accept 360, or derive a number. The ruling taken here is derive, per job. Grounds are the card's own: this repository has twice had a transient fault converted into a cancelled check by a job ceiling (objectui#5304, objectui#6577), and cancelled is a verdict the merge queue cannot tell from failure.

The derivations

Per-job wall clock from the Actions jobs endpoint (completed_at minus started_at — the job's own clock, never the run's total). Successful runs only.

job N window min median p95 max ceiling rule that binds
lint.yml::lint 300 2026-09-05T11:51Z .. 2026-09-06T04:11Z 9s 5m16s 5m46s 7m14s 25 max + 15min = 22.2min
performance-budget.yml::bundle-analysis 300 2026-09-03T11:40Z .. 2026-09-06T04:10Z 57s 3m35s 4m37s 5m23s 25 max + 15min = 20.4min
changeset-release.yml::release 25 2026-08-23T06:08Z .. 2026-09-06T00:23Z 1m23s 2m37s 5m37s 7m15s 40 widened — see below

Rule: ceiling is the smallest round number that is at or above both 3 x max and max + 15min. ⛔ Nothing is inherited from ci.yml's 10/15/20/30/40 — objectui#7048 fences exactly that, and two of the three landing on 25 is arithmetic, not a shared constant.

Why release is 40 and not 25. Its honest slowest run is not the slowest run in its sample. The job's first step, Wait for older release runs to finish, declares MAX_WAIT_SECONDS: '900' for itself, so a contended release may legitimately spend 15 minutes waiting before doing any work. None of the 25 sampled runs was contended, so that quarter-hour appears nowhere in the distribution and has to be added rather than measured. Ceiling = 15min (the declared cap at face value — it is already hard and fail-open, so multiplying it only buys slack for a clock that cannot overrun) + 3 x measured max work (21.8min) = 36.8min, rounded to 40. The plain rule would have given 25, leaving a legitimately contended release (15min + 7m15s = 22m15s) inside 12% of its own ceiling — a ceiling under the job's honest slowest run, which is the mirror hazard.

Two measured corrections to the card

  • changeset-release.yml has two unbounded jobs, not one. lane (uncached) sits above release (cached). The card named only release; the dispatch's file surface covers only it. lane is left alone and recorded in objectui#7956 with the five other uncached jobs that declare no ceiling.
  • performance-budget.yml does not subscribe merge_group. It subscribes push and pull_request only, and 300 sampled runs contain zero merge_group runs. So Bundle Analysis cannot hold the merge queue the way Lint can — what six hours costs there is the pull request and the runner, not a shared serial queue. The exposure is real on its own, so the ceiling still derives; the note is in the workflow so nobody reuses the Lint stakes argument there by mistake.

Both are stated in the workflow comments, not just here.

The pin

scripts/__tests__/workflow-cache-save-bound.test.ts gains one assertion covering the three jobs in both directions: the key missing (the job silently returns to 360) and the number raised above what was derived (objectui#7048's fence). It is the closest existing workflow-shape test — it already parses every workflow's jobs and already reads job-level timeout-minutes — so no new file. Its header prose said "the three jobs here that declare no timeout-minutes at all"; that sentence is now false, and is updated in the same commit.

Ablation, on the committed tree: deleting the job-level timeout-minutes: 25 from lint.yml (verified on disk — anchor count 1 before, 0 after, step-level timeout-minutes: 5 untouched at count 1) turns the run red with 1 failed | 8 passed, naming lint.yml :: job `lint`. Restored with git checkout HEAD --, byte-identical: blob 414d8c0a28b877cc2c164a4946428a3764dddc2c before and after, git diff HEAD empty.

Gates

Run at 248b057ef, verdict lines quoted from the tools themselves:

  • pnpm exec vitest run scripts/Test Files 107 passed (107), Tests 3221 passed (3221)
  • pnpm type-check:scripts — exit 0
  • pnpm lint:root — exit 0 (32 problems (0 errors, 32 warnings), all pre-existing; none in a changed file)
  • pnpm check:control-bytesOK (scanned 6412 tracked text file(s); skipped 85 binary)
  • node scripts/check-changeset-presence.mjsNo source or published contract of a released package changed in this range, so no changeset is owed. Workflow files and a test publish nothing.
  • node scripts/check-governed-queue-guard.mjs --test on all four paths — NOT GOVERNED — 4 path(s) checked against 5 governed surface(s); none matched.

Not done here

  • ⛔ No step-level timeout-minutes touched. The 5 values are objectui#7269's and say DERIVED FOR THIS SITE.
  • ⛔ No change to ci.yml or live-e2e.yml, and no existing ceiling anywhere raised or lowered.
  • ⛔ The six remaining unbounded jobs are out of scope and are recorded in objectui#7956, which stays open.

Generated by Claude Code

…tui#7270)

Three jobs declared no job-level `timeout-minutes`, so their only backstop
was GitHub's 360-minute default. Two of them are required contexts, and this
repository has twice had a transient fault converted into a `cancelled` check
by a job ceiling (objectui#5304, objectui#6577) — a verdict the merge queue
cannot tell from `failure`.

Each ceiling is derived from that job's own measured run distribution, never
inherited from `ci.yml` (objectui#7048 fences that), and the derivation is
recorded beside the key: window, sample size, min/median/p95/max, and the rule.

  - `lint.yml::lint`                      25 (n=300, max 7m14s)
  - `performance-budget.yml::bundle-analysis` 25 (n=300, max 5m23s)
  - `changeset-release.yml::release`      40 (n=25, max 7m15s, plus the
    900-second wait cap the job declares for itself)

`workflow-cache-save-bound.test.ts` gains a pin so removing a key or raising
one of these ceilings is no longer invisible.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uAaxiwgYDybsTNV9xwa1M
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3189.5 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-Bl6k9E1m.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.60KB 116.20KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.37KB
plugin-dashboard (index.js) 132.88KB 34.69KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.56KB 27.70KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.40KB 8.76KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@baozhoutao
baozhoutao marked this pull request as ready for review September 6, 2026 04:39
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 28351f4 Sep 6, 2026
32 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-7270-job-timeouts-derived-per-job branch September 6, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ci] Three cached jobs — two of them required contexts — declare no timeout-minutes at all, so their only backstop is GitHub's 360-minute default

2 participants