You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
[ci] Six more jobs declare no timeout-minutes — the uncached half of the surface objectui#7270 bounded #7956
Found while implementing objectui#7270, as an out-of-scope finding. Recording it rather than acting on it: objectui#7270 scoped itself to the three cached jobs and its dispatch pinned the file surface to those three workflows, so these six were deliberately not touched there.
Attribution as prose: generated by Claude Code, dev session session_013uAaxiwgYDybsTNV9xwa1M, while implementing objectui#7270.
The observation
Measured by parsing every workflow file on 3b30920f1 (control: 34 files enumerated, 42 jobs parsed, non-zero). objectui#7270 bounded three of the nine jobs that declared no job-level timeout-minutes. These six still declare none, so their ceiling is GitHub's documented default of 360 minutes:
workflow
job
changelog.yml
changelog
changeset-release.yml
lane
check-links.yml
check-links
cross-repo-issue-closer.yml
close-foreign-issues
labeler.yml
label
stale.yml
stale
Why objectui#7270 stopped where it did — the selector was real, not an oversight
Its title says cached jobs, and that selector holds under measurement: none of these six uses actions/cache in any form, and none passes cache: to actions/setup-node. They are marketplace-action jobs. So the specific hazard objectui#7270 was written around — a runner-generated cache-save post step that no workflow syntax can bound — is absent here. What remains is the generic exposure: any step in them is backstopped by nothing but 360.
⚠️ One of the six is worth naming on its own: changeset-release.yml now has lane unbounded sitting directly above release bounded at 40. Two jobs, one file, two answers.
⛔ What this does NOT claim
⛔ No hang has been observed in any of these six. This is exposure, not a defect.
⛔ Not a claim that 360 is wrong for them. objectui#7270's own constraint applies unchanged: a ceiling set below a job's honest slowest run converts a working gate into a permanently red one, so any number has to be derived per job from that job's own measured distribution, never inherited.
⛔ Not verified here which, if any, of these six produce required contexts. objectui#7270's stakes argument leaned on two of its three being required on a shared serial queue; that argument has not been re-run for these, and re-running it is part of triage rather than a thing this card asserts.
What triage decides
The same one decision objectui#7270 faced, on a cheaper surface: accept 360 for these six and record that choice in the workflows, or derive a ceiling per job. The recipe for the second option is now written down in three places — the derivation comments objectui#7270 left beside lint.yml::lint, performance-budget.yml::bundle-analysis and changeset-release.yml::release state the window, the sample size, the min/median/p95/max and the rule, and the pin in scripts/__tests__/workflow-cache-save-bound.test.ts is where any new ceiling would be held.
⚠️ One of the six is worth naming on its own: changeset-release.yml now has lane unbounded sitting directly above release bounded at 40. Two jobs, one file, two answers.
⇒ 本席的文件级计数(4)不能否证 lane 无界——它恰恰说明同一个文件里有界与无界并存,这正是卡片指出的那件事。⚠️ 承接席请按 job 而非按文件核对。
⭐ 卡片最该被继承的地方:它证明了上一张卡的选择器是真的,不是疏漏
Its title says cached jobs, and that selector holds under measurement: none of these six uses actions/cache in any form, and none passes cache: to actions/setup-node. They are marketplace-action jobs. So the specific hazard objectui#7270 was written around — a runner-generated cache-save post step that no workflow syntax can bound — is absent here.
⛔ Not verified here which, if any, of these six produce required contexts. objectui#7270's stakes argument leaned on two of its three being required on a shared serial queue; that argument has not been re-run for these, and re-running it is part of triage rather than a thing this card asserts.
a ceiling set below a job's honest slowest run converts a working gate into a permanently red one, so any number has to be derived per job from that job's own measured distribution, never inherited.
Branch: claude/issue-7956-uncached-job-timeouts
Dev: one os-dev subagent of this session (its identity is the branch above; it posts no second claim).
Premise re-checked on origin/main083343dff at 17:15Z: changelog.yml, check-links.yml, cross-repo-issue-closer.yml, labeler.yml, stale.yml carry 0 timeout-minutes; changeset-release.yml carries 5, none on lane (the dev verifies per job, not per file). Triage 5557198251's precondition ① is answered by this seat with a reading the dev inherits and re-checks: the repository ruleset for main (GET /repos/objectstack-ai/objectui/rules/branches/main, readable with the session token; the legacy branch-protection endpoint returns 403) lists the required status checks as Lint, Type Check, Build & E2E, Test (shard 1/4) through (4/4), Build Docs, Changeset Declaration. None of the six jobs produces a required context. So the stakes argument #7270 leaned on does not apply here; what remains is the generic 360-minute exposure, and the recipe is #7270's per-job derivation.
Labels this stroke: pm:queue → pm:dispatched, assignee set, read back below.
Dev: one os-dev subagent of this session on branch claude/issue-7956-uncached-job-timeouts, worktree /home/user/objectui-issue-7956, base origin/main083343dff or newer.
Rulings executed as written (triage 5557198251): precondition ① is answered by the ruleset read in the claim above (none of the six produces a required context) and the dev re-checks it; precondition ② is binding — every ceiling derived per job from that job's own run-time distribution over the Actions API, never one number for six, never inherited from [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 #7270's jobs; a job whose sample cannot support a number gets an explicit accept-360 with a recorded reason rather than an invented ceiling; a job whose honest slowest run would make a ceiling red it goes to needs_decision and the card moves to the decision box for that job.
File surface: the six workflow files and the one pin test. .github/workflows/** is not a governed surface; the dev runs the guard to confirm.
Batch independence at 17:17Z: three devs in flight (objectui#5174 batch 25; objectui#7972 on content/docs/guide/objectos-integration.mdx; this card). Disjoint by construction.
{
"issue": 7956,
"status": "done",
"branch": "claude/issue-7956-uncached-job-timeouts",
"head": "b422e650d",
"base": "cf1d29e2f",
"pr": "https://github.com/objectstack-ai/objectui/pull/8129",
"premise_still_valid": true,
"premise_check": "Re-verified per JOB (not per file) on origin/main cf1d29e2f: all six declare no job-level timeout-minutes. changeset-release.yml carries 5 pre-existing timeout-minutes keys, none of them on `lane`, exactly as triage 5557198251 and the claim said.",
"precondition_1_required_contexts": {
"channel": "repo-scoped REST GET /repos/objectstack-ai/objectui/rules/branches/main, session token, HTTP 200, re-run 2026-09-06T17:20Z",
"legacy_endpoint": "GET /repos/.../branches/main/protection -> HTTP 403, as the claim stated",
"required": ["Lint", "Type Check", "Build & E2E", "Test (shard 1/4)", "Test (shard 2/4)", "Test (shard 3/4)", "Test (shard 4/4)", "Build Docs", "Changeset Declaration"],
"verdict": "none of the six jobs produces a required context; #7270's shared-serial-queue stakes do not apply; what remains is the generic 360-minute exposure"
},
"precondition_2_per_job_derivations": [
{"job": "changeset-release.yml::lane", "population": "last 300 successful runs, 2026-09-02T14:14Z..2026-09-06T17:19Z", "n": 300, "min": "4s", "median": "6s", "p95": "8s", "max": "43s", "max_over_p95": 5.34, "ceiling": 20, "note": "fat tail is runner overhead, not work: on the 3 slowest runs the steps sum to 3s against 41-43s job wall clock"},
{"job": "check-links.yml::check-links", "population": "?event=schedule, total_count 5 = the entire population under today's configuration, 2026-08-09T04:43Z..2026-09-06T04:28Z", "n": 5, "min": "15s", "median": "24s", "p95": "30s", "max": "31s", "max_over_p95": 1.03, "ceiling": 20, "note": "the ?status=success listing (total_count 217) is ALL from 2026-01-24..28 on push/pull_request under the pre-#3449 scope - a different job wearing this name; recorded and NOT used. All 5 sampled runs ended failure but each ran the full sweep (Lychee 9-24s, post steps reached), so the failure is the verdict not an abort, and a red sweep retries and therefore over-states a healthy run"},
{"job": "cross-repo-issue-closer.yml::close-foreign-issues", "population": "last 300 successful runs, 2026-09-03T07:35Z..2026-09-06T17:19Z", "n": 300, "min": "2s", "median": "4s", "p95": "5s", "max": "12s", "max_over_p95": 2.4, "ceiling": 20, "note": "declared-but-unsampled term named in the comment: the github-script step sets retries: 3"},
{"job": "labeler.yml::label", "population": "last 300 successful runs, 2026-09-04T15:06Z..2026-09-06T17:19Z", "n": 300, "min": "3s", "median": "5s", "p95": "8s", "max": "13s", "max_over_p95": 1.62, "ceiling": 20},
{"job": "changelog.yml::changelog", "population": "every run, no status filter, whole retained history", "n": 0, "min": null, "median": null, "p95": null, "max": null, "ceiling": "ACCEPT-360", "note": "total_count 0 runs ever. Control so the zero is a reading: identical call on changeset-release.yml answers 5010 completed. Dispatch-only, never dispatched; the file's own header recorded the same zero before 2026-08-23"},
{"job": "stale.yml::stale", "population": "every completed run, 2026-01-16T01:14Z..2026-09-06T00:19Z", "n": 234, "min": "1s", "median": "2s", "p95": "4s", "max": "4s", "ceiling": "ACCEPT-360", "note": "?status=success returns total_count 0 - never succeeded in the retained history. 10 runs sampled evenly across the window all report one step, `Set up job`, conclusion failure, so the seconds measure how fast the job fails to BEGIN and actions/stale never starts. No distribution of the job doing its work exists; a number here would look derived and be a guess"}
],
"ruling_3_escalation": "not triggered. The slowest run across all six is 43s; no ceiling comes near any job's honest slowest run, so no job was left for the decision box and the PR uses the closing keyword per ruling 7.",
"ruling_5": "changeset-release.yml::release untouched - proved structurally, not by eye: pre-existing-changed=[] for that file in the before/after YAML comparison.",
"files_changed": [
".github/workflows/changelog.yml",
".github/workflows/changeset-release.yml",
".github/workflows/check-links.yml",
".github/workflows/cross-repo-issue-closer.yml",
".github/workflows/labeler.yml",
".github/workflows/stale.yml",
"scripts/__tests__/workflow-cache-save-bound.test.ts"
],
"gates": {
"pinned_to_head": "b422e650d",
"pnpm exec vitest run scripts/__tests__/": "exit 0 - 113 files, 3374 tests passed",
"pnpm exec vitest run workflow-cache-save-bound + ci-cd-pipeline-doc + check-links-workflow + labeler-package-coverage + changeset-release-action-ref-pin": "exit 0 - 77 tests passed",
"pnpm type-check:scripts": "exit 0",
"pnpm check:control-bytes": "exit 0 - scanned 6499 tracked text file(s); skipped 85 binary",
"grep -naP control-byte self-scan over the 7 changed paths": "exit 1 (no hits)",
"node scripts/check-changeset-presence.mjs": "exit 0 - no changeset owed (0 published sources, 0 manifests). No skip-changeset label applied: ci-cd-pipeline-doc.test.ts pins that label as phantom in this repo",
"node scripts/check-governed-queue-guard.mjs --test (7 paths)": "exit 0 - NOT GOVERNED, 7 paths against 5 governed surfaces, none matched (ruling 6 confirmed mechanically)",
"YAML structural before/after per workflow": "exit 0 - all six parse; with timeout-minutes stripped from every job both sides are deep-equal; added=[lane=20, check-links=20, close-foreign-issues=20, label=20]; pre-existing-changed=[] everywhere",
"eslint (narrowed, narrowing measured)": "exit 0 - 1 file linted (from --format json array length), 0 errors, 0 warnings. Population: only one .ts file changed and eslint reads no .yml. Invariance: eslint.config.js extends tseslint.configs.recommended, NOT recommendedTypeChecked, and declares no parserOptions.project / projectService, so type-aware linting is off and this diff cannot move the verdict on any untouched file. Repo-wide run left to CI",
"ci_status": "in_progress - reported at draft-PR time per the dispatch contract; CI convergence is the PM's read, not this dev's"
},
"ablation": {
"method": "run against the COMMITTED implementation; restore leg `git checkout HEAD -- PATH`; trap on EXIT INT TERM with absolute paths; each mutation proved on disk by an occurrence count in BOTH directions before the test ran; byte-identity proved after by comparing git hash-object against the HEAD blob. No build/dist step involved - the pin reads workflow YAML from disk and vitest runs the .ts source",
"control_unmutated": "exit 0, 11 passed",
"leg_A_delete_ceiling_labeler": "occurrences 1 -> 0; exit 1; message `these jobs declare no job-level ...`",
"leg_B_copy_20_onto_stale": "occurrences 0 -> 1; exit 1; message `these jobs were deliberately left at the 360-minute default ...`",
"leg_C_strip_card_ref_changelog": "occurrences 3 -> 0; exit 1; message `these jobs run under the 360-minute default with nothing ...`",
"restore_proof": "git diff HEAD empty; all three blobs hash-equal to their HEAD blobs (labeler e81060dde..., stale 019e6ad7f..., changelog 3785572d1...)",
"direction": "as predicted - three distinct branches, three distinct messages"
},
"mcp_calls": "7 - search_issues x3 (2 dedup queries + 1 known-hit control), issue_write x2 (the two findings), create_pull_request x1, add_issue_comment x1. Everything else went over repo-scoped REST or git",
"deviations": [
"A2 FALSIFIED in two places, and both changed the sampling. (a) changelog.yml is NOT schedule-driven - it declares workflow_dispatch only, and has 0 runs ever, so it took the accept-360 branch. (b) check-links.yml is NOT per-PR - it is workflow_dispatch + a weekly cron, and its per-PR history is the pre-#3449 configuration under different triggers, which is why its sample is n=5 rather than the 217 the success filter offers.",
"A3 PARTLY FALSIFIED: workflow-cache-save-bound.test.ts holds no `exactly N unbounded jobs remain` count, so no count had to move. It is a table extension plus one NEW assertion kind - a pin on the ABSENCE of a ceiling plus the presence of its recorded reason, which the accept-360 half needs and #7270's shape did not provide.",
"A1 HELD: repo-scoped Actions REST readable with the session token; no MCP actions_list / actions_get fallback needed.",
"A4 and A5 HELD: nothing else touches these workflows; ci-cd-pipeline-doc.test.ts did not move and passes. objectui#8122's stale count in changeset-release.yml's step header was not touched.",
"Ruling 4's escape hatch NOT needed: the existing pin's shape did fit, so no new file was created. #7270's own entry was left intact and a sibling `it()` added, because its failure text (`two of them are required contexts and one is the publish lane`) is false for these four.",
"Lint narrowed to the changed file rather than repo-wide, with the three-part narrowing evidence recorded above; declared, not silent."
],
"open_questions": [],
"out_of_scope_findings": [
"filed as objectui#8126: stale.yml has never succeeded - 234 scheduled runs, 234 failures over 8 months, every one in `Set up job` before actions/stale starts, so every automation that file declares has been inert. No labels, no assignee. This is WHY stale took the accept-360 branch.",
"filed as objectui#8128: every scheduled check-links sweep since the cron landed is red (5 of 5) and the last green run predates objectui#3449's scope fix, so the sweep works and its answer reaches nobody. No labels, no assignee. This is WHY check-links' sample is the 5 scheduled runs and not the 217 successes.",
"NOT filed, offered to the PM as an observation only: changelog.yml has a workflow-registry entry (id 223701081) while reporting 0 runs ever. AGENTS.md's `Actions workflow registry` section states an entry is created by a workflow's first run on any ref. One of the two readings is incomplete. Not chased - AGENTS.md is a governed surface and this is outside this card."
],
"summary": "Bounded four of the six jobs at 20 minutes each, every number derived from that job's own measured run distribution over repo-scoped Actions REST by objectui#7270's rule, with the window, sample size, min/median/p95/max and the arithmetic written beside each key. The four coincide because the rule's additive term dominates for any job under 7.5 minutes; their maxima are 43s, 31s, 13s and 12s, so it is arithmetic and not an inherited constant, and the PR and the pin both say so explicitly. The other two took the triage's accept-360 branch with the reason recorded in the workflow: changelog.yml::changelog has 0 runs ever, and stale.yml::stale has 234 runs of which 0 succeeded and all fail in `Set up job` before its action starts, so neither has a distribution of the job doing its work. The pin holds both halves - the four ceilings, and the two deliberate absences plus their recorded reason - and all three of its new branches were shown to go red under mutation and the tree restored byte-identically. The assignee was set by the PM before dispatch and was not written by this dev; the newest Claim on the card names this branch."
}
Verified on origin/claude/issue-7956-uncached-job-timeouts (merge base cf1d29e2f):
One commit, seven files: the six workflow files (additions only; every non-comment added line is one of four timeout-minutes: 20 keys, on changeset-release.yml::lane, check-links, close-foreign-issues, label; the rest are the derivation comments) and scripts/__tests__/workflow-cache-save-bound.test.ts +167/−2. No - line touches changeset-release.yml::release's 40.
PR body's first line is Fixes #7956; the commit message says Part of objectui#7956 (not a closing keyword, and the PR body governs). All six jobs are either bounded or explicitly accepted at 360 with the reason recorded, so Fixes is the right shape under ruling 7.
Precondition ① re-run by the dev on the ruleset endpoint (HTTP 200): none of the six produces a required context. Precondition ② executed per job over the Actions REST API with window, sample size, min/median/p95/max recorded: four ceilings of 20 that coincide by arithmetic (the rule's additive term dominates for jobs whose maxima are 43s, 31s, 13s, 12s), stated as such; two accept-360 decisions where no distribution of the job doing its work exists (changelog has 0 runs ever; stale has 234 runs, 0 successes, all failing in Set up job).
The pin now holds both halves (the four ceilings, and the two deliberate absences with their recorded reason), and the dev's three ablation legs show each new branch going red under mutation with byte-identical restores.
Head check-runs at 17:44Z: Test shards, Type Check, Lint and three doc checks still running; everything else green.
Findings against the dispatch, acknowledged: A2 falsified twice in the direction that changed the sampling (changelog.yml is dispatch-only with no runs; check-links.yml is cron plus dispatch, and its 217 successes predate objectui#3449's scope fix, so its honest sample is the 5 scheduled runs). A3 partly falsified (no unbounded-count assertion existed; the new absence-plus-reason assertion is the pin's addition). Ruling 3's escalation was not triggered (slowest run 43s). Out-of-scope findings #8126 (stale.yml has never succeeded) and #8128 (every scheduled check-links sweep is red and reaches nobody) were filed with no labels; the seat adds the finding label to both so the triage sweep's first-grading path sees them (no domain:*, that is triage's).
Next: standing-down note on #8129 for Live E2E, CI convergence on b422e650d, then ready → Governed Surface Queue Guard → auto-merge (squash) → merge queue → content probe on origin/main → LANDED. On MERGED this card closes via Fixes and the seat strips pm:dispatched and clears the assignee.
Activity
os-zhuang commentedon Sep 6, 2026
分诊:
domain:devx/enhancement+tooling+finding/pm:queue/priority:p3落点核实(
origin/main)——六个中五个逐条成立,第六个需要一句更精确的表述范围内活控制:
lint.yml的timeout-minutes命中 4 ⇒ 模式有效、目录可搜 ⇒ 上面五个 0 是读数。⇒
changeset-release.yml的读数正好印证卡片自己单独点名的那一条:⇒ 本席的文件级计数(4)不能否证⚠️ 承接席请按 job 而非按文件核对。
lane无界——它恰恰说明同一个文件里有界与无界并存,这正是卡片指出的那件事。⭐ 卡片最该被继承的地方:它证明了上一张卡的选择器是真的,不是疏漏
⇒ 这不是「#7270 漏了六个」,是「#7270 的总体定义精确,这六个在它之外」。 ⭐ 一张后续卡主动为前一张卡的范围辩护,而不是把它说成不完整——本席特别记名。
⇒ 剩下的是通用暴露:这六个 job 的任何一步只由 GitHub 记录的默认 360 分钟兜底。
定级理由
domain:devx:.github/workflows/**⇒ objectui 的domain:devx。enhancement而非bug:⭐ 卡片自己划清了,本席逐字背书——⇒ main 上没有东西是假的。加
timeout-minutes是新增约束,属加固。p3:无已观察到的故障;六个都是 marketplace-action job,且卡片明确没有验证它们中是否有产出 required context 的(见下)。⛔ 不降更低:一个无界的 job 在 GitHub 的共享队列上最坏能占 360 分钟,而 [ci] Three cached jobs — two of them required contexts — declare no
timeout-minutesat all, so their only backstop is GitHub's 360-minute default #7270 的 stakes 论证正是建立在「必需上下文 + 共享串行队列」上。为什么是
pm:queue而不是决定箱卡片写「What triage decides: the same one decision objectui#7270 faced」。本席判定这不是决定箱:
timeout-minutesat all, so their only backstop is GitHub's 360-minute default #7270 已经答过同一个问题,并且把答法写下来了三次——卡片自陈:"the derivation comments objectui#7270 left besidelint.yml::lint,performance-budget.yml::bundle-analysisandchangeset-release.yml::releasestate the window, the sample size, the min/median/p95/max and the rule, and the pin inscripts/__tests__/workflow-cache-save-bound.test.tsis where any new ceiling would be held."⛔ 若承接席在测量后判定某个 job 的诚实最慢运行接近或超过一个合理上限(⇒ 设界会把一道能用的门变成永久红),那才是决定箱——请在 PR 里说明并把本卡改挂
needs-user-decision。① 先测这六个里有没有产出 required context 的。
⇒ 本席也没测(那需要读分支保护设置)。⚠️ 这一步决定这六个是否值得逐个派生,还是可以一律接受 360 并记录——⇒ 它必须排在任何编辑之前。
② ⛔ 任何数字都必须从该 job 自己的实测分布派生,绝不继承。
⇒ ⛔ 不要给这六个统一设一个数。 本 lane 本轮在 objectstack 侧刚见过反例:#16173——
scripts/test-shard-timings.json对 CLI 包低估 ~2.6 倍,导致Test Core (1/6)撞上 30 分钟墙被杀,而 rollup 读成绿。⇒ 一个从错误分布派生的上限,比没有上限更危险。Refs:objectui#7270(cached 那一半,本卡的来源,其配方是这里的路线)· objectui#7048 · objectui#6577 · objectui#5304。
⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box(本会话为
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
Claim:
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T17:16Z.Branch:
claude/issue-7956-uncached-job-timeoutsDev: one
os-devsubagent of this session (its identity is the branch above; it posts no second claim).Premise re-checked on
origin/main083343dffat 17:15Z:changelog.yml,check-links.yml,cross-repo-issue-closer.yml,labeler.yml,stale.ymlcarry 0timeout-minutes;changeset-release.ymlcarries 5, none onlane(the dev verifies per job, not per file). Triage5557198251's precondition ① is answered by this seat with a reading the dev inherits and re-checks: the repository ruleset formain(GET /repos/objectstack-ai/objectui/rules/branches/main, readable with the session token; the legacy branch-protection endpoint returns 403) lists the required status checks asLint,Type Check,Build & E2E,Test (shard 1/4)through(4/4),Build Docs,Changeset Declaration. None of the six jobs produces a required context. So the stakes argument #7270 leaned on does not apply here; what remains is the generic 360-minute exposure, and the recipe is #7270's per-job derivation.Labels this stroke:
pm:queue→pm:dispatched, assignee set, read back below.Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
Dispatched — R46,
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, 2026-09-06T17:17Z.os-devsubagent of this session on branchclaude/issue-7956-uncached-job-timeouts, worktree/home/user/objectui-issue-7956, baseorigin/main083343dffor newer.5557198251): precondition ① is answered by the ruleset read in the claim above (none of the six produces a required context) and the dev re-checks it; precondition ② is binding — every ceiling derived per job from that job's own run-time distribution over the Actions API, never one number for six, never inherited from [ci] Three cached jobs — two of them required contexts — declare notimeout-minutesat all, so their only backstop is GitHub's 360-minute default #7270's jobs; a job whose sample cannot support a number gets an explicit accept-360 with a recorded reason rather than an invented ceiling; a job whose honest slowest run would make a ceiling red it goes toneeds_decisionand the card moves to the decision box for that job.timeout-minutesat all, so their only backstop is GitHub's 360-minute default #7270's — job-leveltimeout-minuteswith the derivation comment (window, sample, min/median/p95/max, rule) and the ceilings held in the existing pinscripts/__tests__/workflow-cache-save-bound.test.ts(or the most local existing pin if its shape does not fit).changeset-release.yml::release's 40 is not touched, and the stale count objectui#8122 noted in that file's step header is not this card's..github/workflows/**is not a governed surface; the dev runs the guard to confirm.content/docs/guide/objectos-integration.mdx; this card). Disjoint by construction.Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
os-dev-report
{ "issue": 7956, "status": "done", "branch": "claude/issue-7956-uncached-job-timeouts", "head": "b422e650d", "base": "cf1d29e2f", "pr": "https://github.com/objectstack-ai/objectui/pull/8129", "premise_still_valid": true, "premise_check": "Re-verified per JOB (not per file) on origin/main cf1d29e2f: all six declare no job-level timeout-minutes. changeset-release.yml carries 5 pre-existing timeout-minutes keys, none of them on `lane`, exactly as triage 5557198251 and the claim said.", "precondition_1_required_contexts": { "channel": "repo-scoped REST GET /repos/objectstack-ai/objectui/rules/branches/main, session token, HTTP 200, re-run 2026-09-06T17:20Z", "legacy_endpoint": "GET /repos/.../branches/main/protection -> HTTP 403, as the claim stated", "required": ["Lint", "Type Check", "Build & E2E", "Test (shard 1/4)", "Test (shard 2/4)", "Test (shard 3/4)", "Test (shard 4/4)", "Build Docs", "Changeset Declaration"], "verdict": "none of the six jobs produces a required context; #7270's shared-serial-queue stakes do not apply; what remains is the generic 360-minute exposure" }, "precondition_2_per_job_derivations": [ {"job": "changeset-release.yml::lane", "population": "last 300 successful runs, 2026-09-02T14:14Z..2026-09-06T17:19Z", "n": 300, "min": "4s", "median": "6s", "p95": "8s", "max": "43s", "max_over_p95": 5.34, "ceiling": 20, "note": "fat tail is runner overhead, not work: on the 3 slowest runs the steps sum to 3s against 41-43s job wall clock"}, {"job": "check-links.yml::check-links", "population": "?event=schedule, total_count 5 = the entire population under today's configuration, 2026-08-09T04:43Z..2026-09-06T04:28Z", "n": 5, "min": "15s", "median": "24s", "p95": "30s", "max": "31s", "max_over_p95": 1.03, "ceiling": 20, "note": "the ?status=success listing (total_count 217) is ALL from 2026-01-24..28 on push/pull_request under the pre-#3449 scope - a different job wearing this name; recorded and NOT used. All 5 sampled runs ended failure but each ran the full sweep (Lychee 9-24s, post steps reached), so the failure is the verdict not an abort, and a red sweep retries and therefore over-states a healthy run"}, {"job": "cross-repo-issue-closer.yml::close-foreign-issues", "population": "last 300 successful runs, 2026-09-03T07:35Z..2026-09-06T17:19Z", "n": 300, "min": "2s", "median": "4s", "p95": "5s", "max": "12s", "max_over_p95": 2.4, "ceiling": 20, "note": "declared-but-unsampled term named in the comment: the github-script step sets retries: 3"}, {"job": "labeler.yml::label", "population": "last 300 successful runs, 2026-09-04T15:06Z..2026-09-06T17:19Z", "n": 300, "min": "3s", "median": "5s", "p95": "8s", "max": "13s", "max_over_p95": 1.62, "ceiling": 20}, {"job": "changelog.yml::changelog", "population": "every run, no status filter, whole retained history", "n": 0, "min": null, "median": null, "p95": null, "max": null, "ceiling": "ACCEPT-360", "note": "total_count 0 runs ever. Control so the zero is a reading: identical call on changeset-release.yml answers 5010 completed. Dispatch-only, never dispatched; the file's own header recorded the same zero before 2026-08-23"}, {"job": "stale.yml::stale", "population": "every completed run, 2026-01-16T01:14Z..2026-09-06T00:19Z", "n": 234, "min": "1s", "median": "2s", "p95": "4s", "max": "4s", "ceiling": "ACCEPT-360", "note": "?status=success returns total_count 0 - never succeeded in the retained history. 10 runs sampled evenly across the window all report one step, `Set up job`, conclusion failure, so the seconds measure how fast the job fails to BEGIN and actions/stale never starts. No distribution of the job doing its work exists; a number here would look derived and be a guess"} ], "ruling_3_escalation": "not triggered. The slowest run across all six is 43s; no ceiling comes near any job's honest slowest run, so no job was left for the decision box and the PR uses the closing keyword per ruling 7.", "ruling_5": "changeset-release.yml::release untouched - proved structurally, not by eye: pre-existing-changed=[] for that file in the before/after YAML comparison.", "files_changed": [ ".github/workflows/changelog.yml", ".github/workflows/changeset-release.yml", ".github/workflows/check-links.yml", ".github/workflows/cross-repo-issue-closer.yml", ".github/workflows/labeler.yml", ".github/workflows/stale.yml", "scripts/__tests__/workflow-cache-save-bound.test.ts" ], "gates": { "pinned_to_head": "b422e650d", "pnpm exec vitest run scripts/__tests__/": "exit 0 - 113 files, 3374 tests passed", "pnpm exec vitest run workflow-cache-save-bound + ci-cd-pipeline-doc + check-links-workflow + labeler-package-coverage + changeset-release-action-ref-pin": "exit 0 - 77 tests passed", "pnpm type-check:scripts": "exit 0", "pnpm check:control-bytes": "exit 0 - scanned 6499 tracked text file(s); skipped 85 binary", "grep -naP control-byte self-scan over the 7 changed paths": "exit 1 (no hits)", "node scripts/check-changeset-presence.mjs": "exit 0 - no changeset owed (0 published sources, 0 manifests). No skip-changeset label applied: ci-cd-pipeline-doc.test.ts pins that label as phantom in this repo", "node scripts/check-governed-queue-guard.mjs --test (7 paths)": "exit 0 - NOT GOVERNED, 7 paths against 5 governed surfaces, none matched (ruling 6 confirmed mechanically)", "YAML structural before/after per workflow": "exit 0 - all six parse; with timeout-minutes stripped from every job both sides are deep-equal; added=[lane=20, check-links=20, close-foreign-issues=20, label=20]; pre-existing-changed=[] everywhere", "eslint (narrowed, narrowing measured)": "exit 0 - 1 file linted (from --format json array length), 0 errors, 0 warnings. Population: only one .ts file changed and eslint reads no .yml. Invariance: eslint.config.js extends tseslint.configs.recommended, NOT recommendedTypeChecked, and declares no parserOptions.project / projectService, so type-aware linting is off and this diff cannot move the verdict on any untouched file. Repo-wide run left to CI", "ci_status": "in_progress - reported at draft-PR time per the dispatch contract; CI convergence is the PM's read, not this dev's" }, "ablation": { "method": "run against the COMMITTED implementation; restore leg `git checkout HEAD -- PATH`; trap on EXIT INT TERM with absolute paths; each mutation proved on disk by an occurrence count in BOTH directions before the test ran; byte-identity proved after by comparing git hash-object against the HEAD blob. No build/dist step involved - the pin reads workflow YAML from disk and vitest runs the .ts source", "control_unmutated": "exit 0, 11 passed", "leg_A_delete_ceiling_labeler": "occurrences 1 -> 0; exit 1; message `these jobs declare no job-level ...`", "leg_B_copy_20_onto_stale": "occurrences 0 -> 1; exit 1; message `these jobs were deliberately left at the 360-minute default ...`", "leg_C_strip_card_ref_changelog": "occurrences 3 -> 0; exit 1; message `these jobs run under the 360-minute default with nothing ...`", "restore_proof": "git diff HEAD empty; all three blobs hash-equal to their HEAD blobs (labeler e81060dde..., stale 019e6ad7f..., changelog 3785572d1...)", "direction": "as predicted - three distinct branches, three distinct messages" }, "mcp_calls": "7 - search_issues x3 (2 dedup queries + 1 known-hit control), issue_write x2 (the two findings), create_pull_request x1, add_issue_comment x1. Everything else went over repo-scoped REST or git", "deviations": [ "A2 FALSIFIED in two places, and both changed the sampling. (a) changelog.yml is NOT schedule-driven - it declares workflow_dispatch only, and has 0 runs ever, so it took the accept-360 branch. (b) check-links.yml is NOT per-PR - it is workflow_dispatch + a weekly cron, and its per-PR history is the pre-#3449 configuration under different triggers, which is why its sample is n=5 rather than the 217 the success filter offers.", "A3 PARTLY FALSIFIED: workflow-cache-save-bound.test.ts holds no `exactly N unbounded jobs remain` count, so no count had to move. It is a table extension plus one NEW assertion kind - a pin on the ABSENCE of a ceiling plus the presence of its recorded reason, which the accept-360 half needs and #7270's shape did not provide.", "A1 HELD: repo-scoped Actions REST readable with the session token; no MCP actions_list / actions_get fallback needed.", "A4 and A5 HELD: nothing else touches these workflows; ci-cd-pipeline-doc.test.ts did not move and passes. objectui#8122's stale count in changeset-release.yml's step header was not touched.", "Ruling 4's escape hatch NOT needed: the existing pin's shape did fit, so no new file was created. #7270's own entry was left intact and a sibling `it()` added, because its failure text (`two of them are required contexts and one is the publish lane`) is false for these four.", "Lint narrowed to the changed file rather than repo-wide, with the three-part narrowing evidence recorded above; declared, not silent." ], "open_questions": [], "out_of_scope_findings": [ "filed as objectui#8126: stale.yml has never succeeded - 234 scheduled runs, 234 failures over 8 months, every one in `Set up job` before actions/stale starts, so every automation that file declares has been inert. No labels, no assignee. This is WHY stale took the accept-360 branch.", "filed as objectui#8128: every scheduled check-links sweep since the cron landed is red (5 of 5) and the last green run predates objectui#3449's scope fix, so the sweep works and its answer reaches nobody. No labels, no assignee. This is WHY check-links' sample is the 5 scheduled runs and not the 217 successes.", "NOT filed, offered to the PM as an observation only: changelog.yml has a workflow-registry entry (id 223701081) while reporting 0 runs ever. AGENTS.md's `Actions workflow registry` section states an entry is created by a workflow's first run on any ref. One of the two readings is incomplete. Not chased - AGENTS.md is a governed surface and this is outside this card." ], "summary": "Bounded four of the six jobs at 20 minutes each, every number derived from that job's own measured run distribution over repo-scoped Actions REST by objectui#7270's rule, with the window, sample size, min/median/p95/max and the arithmetic written beside each key. The four coincide because the rule's additive term dominates for any job under 7.5 minutes; their maxima are 43s, 31s, 13s and 12s, so it is arithmetic and not an inherited constant, and the PR and the pin both say so explicitly. The other two took the triage's accept-360 branch with the reason recorded in the workflow: changelog.yml::changelog has 0 runs ever, and stale.yml::stale has 234 runs of which 0 succeeded and all fail in `Set up job` before its action starts, so neither has a distribution of the job doing its work. The pin holds both halves - the four ceilings, and the two deliberate absences plus their recorded reason - and all three of its new branches were shown to go red under mutation and the tree restored byte-identically. The assignee was set by the PM before dispatch and was not written by this dev; the newest Claim on the card names this branch." }Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
ACCEPT — PR #8129 (head
b422e650d), verified against GitHub and the treedomain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, read 2026-09-06T17:44Z.Verified on
origin/claude/issue-7956-uncached-job-timeouts(merge basecf1d29e2f):timeout-minutes: 20keys, onchangeset-release.yml::lane,check-links,close-foreign-issues,label; the rest are the derivation comments) andscripts/__tests__/workflow-cache-save-bound.test.ts+167/−2. No-line toucheschangeset-release.yml::release's 40.Fixes #7956; the commit message saysPart of objectui#7956(not a closing keyword, and the PR body governs). All six jobs are either bounded or explicitly accepted at 360 with the reason recorded, soFixesis the right shape under ruling 7.changeloghas 0 runs ever;stalehas 234 runs, 0 successes, all failing inSet up job).Findings against the dispatch, acknowledged: A2 falsified twice in the direction that changed the sampling (
changelog.ymlis dispatch-only with no runs;check-links.ymlis cron plus dispatch, and its 217 successes predate objectui#3449's scope fix, so its honest sample is the 5 scheduled runs). A3 partly falsified (no unbounded-count assertion existed; the new absence-plus-reason assertion is the pin's addition). Ruling 3's escalation was not triggered (slowest run 43s). Out-of-scope findings #8126 (stale.ymlhas never succeeded) and #8128 (every scheduledcheck-linkssweep is red and reaches nobody) were filed with no labels; the seat adds thefindinglabel to both so the triage sweep's first-grading path sees them (nodomain:*, that is triage's).Next: standing-down note on #8129 for Live E2E, CI convergence on
b422e650d, then ready → Governed Surface Queue Guard → auto-merge (squash) → merge queue → content probe onorigin/main→ LANDED. On MERGED this card closes viaFixesand the seat stripspm:dispatchedand clears the assignee.Generated by Claude Code