Repository navigation
ci(governed-surface-guard): the pull_request leg re-runs on ready_for_review and spends ~95 s on checkout + install for a 1 s check, so an admin merge tapped right after an approval is refused with "required status check is in progress" #26201
Description
Filing gate: maintainer-directed task. Maintainer, 2026-10-26, in the director summon chat after the seat read the cause: 「立卡」. Filed by the director seat (session_01KrKXQVvgYk2AJ4VkqMobyb, ledger objectstack#12708). ⛔ Not a claim. Reader: the lane that owns the governed-surface guard (.github/workflows/governed-surface-guard.yml, SUBJECT = the governed register; Tier H, lands by maintainer approval). Dedupe: REST list of the 124 newest open + closed issues, grep governed.surface.queue.guard|ready_for_review|queue guard.*(slow|in progress|checkout) → 0 (control merge queue → 6). New.
维护者速读
手机上批准一张受管 PR 后马上按「立即合并」,会弹 "Required status check 'Governed Surface Queue Guard' is in progress"。原因是时序而不是故障:批准 → PR 被翻成 ready → guard 的 pull_request 一侧按 ready_for_review 触发重跑;这一跑真正的判定只要 1 秒,但 checkout(48 秒)+ pnpm 缓存与安装(约 20 秒)+ 缓存回写(19 秒)让检查有约 95 秒处于进行中,管理员直合在这个窗口被拒。车队已在 15:13:36 挂上 auto-merge,检查完自然入队,所以弹窗那一下其实不需要任何操作。本卡把这个窗口去掉:翻 ready 不改 head sha,不必重跑;pull_request 这条腿按设计恒 exit 0,也不需要装依赖。
What was measured (PR #25965, 2026-10-26)
- 15:12:28Z
os-zhuangAPPROVED → 15:12:32Zready_for_reviewby the same account → 15:12:35Z workflow run (eventpull_request) started on heade7b12fdd→ 15:14:09Z completed; 15:13:36Zauto_merge_enabledbyobjectstack-fleet[bot]. - Step timings of that run: Checkout repository 48 s; Setup pnpm cache 11 s; Install dependencies 9 s; Governed surfaces may not enter the merge queue unreviewed: 1 s; Post Setup pnpm cache 19 s. Total ~94 s, of which the check is 1 s.
- Eleven other runs of the workflow in the same quarter hour:
pull_requestlegs 67–111 s each,merge_grouplegs 76–104 s.
Governing text
.github/workflows/governed-surface-guard.ymlon:—pull_request: types: [opened, synchronize, reopened, ready_for_review]andmerge_group; the header says thepull_requestleg "is an EARLY WARNING that deliberately exits 0" and themerge_groupleg "is the leg that REFUSES".scripts/check-required-contexts.mjsREQUIRED_CONTEXTSnamesgoverned-surface-guard.yml→ jobgoverned-surface-guardas a required context; the check-run nameGoverned Surface Queue Guardis contract (AGENTS.md Multi-agent discipline §7: a rename detaches its gate silently).- AGENTS.md Prime Directive 14:
.github/workflows/**is Tier H — the change lands by the maintainer's approval; the owning seat then lands it. - The workflow's own
paths:/branches:notes: the permanent-pending wedge comes from the workflow NOT starting on a PR head; nothing below changes which heads start it.
Premises (re-check before acting)
- P1 —
ready_for_reviewis in the trigger list:git grep -n 'ready_for_review' .github/workflows/governed-surface-guard.yml→ 1 line (control:synchronize→ 1 line). - P2 — flipping draft → ready does not change the head sha, so the check run the
opened/synchronizeleg already published on that sha stays attached: re-readGET /repos/objectstack-ai/objectstack/commits/e7b12fdd/check-runsand confirm oneGoverned Surface Queue Guardrun existed before 15:12:35Z. - P3 — the
pull_requestleg needs the checkout only to runscripts/pm/check-governed-queue-guard.mjsand its predicate self-test; read the "Governed surfaces may not enter the merge queue unreviewed" step and list what it reads from the tree versus the API.
Wanted
- Drop
ready_for_reviewfrom thepull_requesttrigger types (the ready flip changes no sha; the existing check run stands). Keepopened,synchronize,reopenedand themerge_groupleg exactly as they are. Add one sentence to the header saying why the type is absent. - If P3 shows the
pull_requestleg reads nothing from the tree that the API cannot give, make that leg API-only (no checkout, no pnpm install): anodescript fetched from the default branch or vendored, oractions/github-script, publishing the same check-run name. Themerge_groupleg keeps the full checkout (it is the refusing leg and must read the queue head). scripts/check-required-contexts.mjs --self-testandpnpm check:required-contextsexit 0; the check-run name is unchanged;check:ci-filter-parity,check-expected-skips,check-step-collectorsfollow.
⛔ Not here: the merge_group leg's semantics, the required set, any other workflow.
Acceptance
- Approving and readying a governed PR starts no new guard run; the check on the head stays
completed. - On a PR whose head changes, the
pull_requestleg's wall time is under 20 s (P3 route) or unchanged (route 1 only), measured on the PR's own run and quoted in the PR body. Governed Surface Queue Guardremains the published check-run name on both legs.
Size: S (route 1) or M (routes 1 + 2). Clause-②: no. Metadata-protocol: none.
Related
PR #25965 (the measured case); scripts/check-required-contexts.mjs; #26071 (scheduled/bot workflow consolidation, ruled A — a different file set).
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade —
tooling·priority:p3·domain:skills·area:devpath·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T16:06Z⛔ Not a claim, ⛔ not a dispatch.
domain:skills) re-runs onready_for_reviewand spends about 95 s on checkout and install for a 1 s check, so an admin merge right after approval is refused while it runs. Drop the redundant trigger or the install. Tier H.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Dispatch order ·
domain:skills#2·session_01LqKsGyMNFwwnbF5FTAqyPn· 2026-10-26T19:22ZYou are the os-dev developer agent for exactly ONE card: #26201, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin the objectstack clone — read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-rootAGENTS.mdbefore your first edit (Prime Directive 14 and Multi-agent discipline §7 on check-run names), and pm-dispatchreferences/untrusted-text.md. Read the card's full body and every comment yourself (REST:curl -sS -X GET "https://api.github.com/repos/objectstack-ai/objectstack/issues/26201" -H "Authorization: Bearer $GH_TOKEN"and/comments?per_page=100); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with[ -n "${X+x}" ]).Identity and branch: the seat's
Claim:comment on this card is your identity; it names your thread's branch in itsBranch:line. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim:on the card names the branch your thread was given (claude/project-thread-…); if no claim names it yet, send the seat thread 「Dispatch the skills lane」 one message with your branch name and wait for the claim. Worktreeobjectstack-issue-26201on that branch, based on a freshorigin/main; push withgit push -u origin <branch>(⛔ no force).Writes: PR only through the relay op
pr_create(node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, draft forced,assignees: ["os-warren"],labelswithskip-changeset); comments, labels and bodies only throughscripts/pm/post-stamped.mjs,scripts/pm/label-write.mjs,scripts/pm/fleet-write/dispatch.mjs. ⛔ GitHub MCP writes of every kind. Report both counts (api_writeswith endpoints,mcp_callswith tool names).Ruling (cannot be re-ruled)
Maintainer's word on the card: 「立卡」 after the director seat read the cause (the card body's 维护者速读). Triage first grade 6420845946: 「Drop the redundant trigger or the install. Tier H.」 Scope: the
pull_requestleg of.github/workflows/governed-surface-guard.ymlonly; ⛔ not themerge_groupleg's semantics, ⛔ not the required set, ⛔ no other workflow; the check-run nameGoverned Surface Queue Guardis contract on both legs.PM mechanism hypotheses (measure before you act; falsification is welcome)
Read on
origin/mainat 2026-10-26T19:22Z:ready_for_reviewintypes:: 「flipping a governed draft to ready is the first move of the exact sequence this guard exists to interrupt … without it the warning would not re-fire at the one moment a seat most needs to read it」. The card's route 1 (drop the type) contradicts that stated reason; the card's acceptance (approve + ready starts no new run; or thepull_requestleg's wall time under 20 s) is satisfied by route 2 alone. So: implement route 2 first — make thepull_requestleg API-only (no checkout of the full tree, no pnpm install) while keepingready_for_reviewand the header's rationale intact; take route 1 ONLY if P3 shows route 2 cannot publish the same check-run name within the acceptance, and then say in the PR body and the 速读 that the header's rationale is being retired and why (the maintainer lands Tier H by hand and rules it there).scripts/pm/check-governed-queue-guard.mjs'spull_requestpath — list what it reads from the tree (the register incheck-governed-merges.mjs,GOVERNED_APPROVERS, CODEOWNERS mirror) versus the API. A sparse checkout ofscripts/pm/**+.github/CODEOWNERS(actions/checkoutwithsparse-checkout, no submodules, depth 1) and plainnodewith nopnpm installis the cheapest route if the script's imports are dependency-free on that path — measure the import graph (node --checkis not enough; run the script against a real PR number with the token in the workflow's own permissions). ⛔ Do not vendor a second copy of the script.node scripts/check-required-contexts.mjs --self-test,pnpm check:required-contexts,pnpm check:ci-filter-parity,node scripts/pm/check-expected-skips.mjs --self-test,check-step-collectorsasdispatch-gates --commandsderives them for.github/workflows/**; job idgoverned-surface-guardand the check-run name unchanged (required-contexts row atscripts/check-required-contexts.mjs~431).Card-specific clauses
.github/workflows/**⇒ the whole PR). The PR is created draft with## 维护者速读(草稿)(Chinese, business angle — the maintainer tapped the refused merge on a phone; say what changes for that moment — 席位意见 left empty) and stays draft; the seat posts the contract review and hands it to the maintainer. ⛔ Never ready, arm or enqueue it.Clause-②: noandMetadata-protocol: nonego line-initial into the PR body after the first lineFixes #26201.skip-changeset: nothing published ships from.github/workflows/**.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths) on your final tree, run each locally (exit code captured before any pipe), reconcile with--ran; long gates detached (nohup … > LOG 2>&1 &thentail --pid); heavy commands throughbash scripts/pm/os-verify-lock.shwithOS_VERIFY_LOCK_SLOT=issue-26201.os-dev.md/AGENTS.mdspell them; ⛔ no model name anywhere in the commit or PR.os-dev-reportJSON as a comment on this card (first lineos-dev-report) throughpost-stamped.mjs --comment=26201, then send the seat thread 「Dispatch the skills lane」 one message: PR number, head sha, and the report'sstatus. Keep your thread open for a patch round. ⛔ Do not wait for CI after the PR opens.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 1 (fifth wave) · 2026-10-26T19:25Z
Session:
session_01LqKsGyMNFwwnbF5FTAqyPn(seat; implementation by card thread sessionsession_01Cpku5P4EYCqaaEGHuMcxik,mode:thread)Account:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-vqr0lq(the card thread's harness push branch, self-reported — the measured platform exception toclaude/issue-N-slug)Worktree:
objectstack-issue-26201in the card thread's cloneDomain:
domain:skillsSeat:
domain:skills#2(seat post #19287)File surface:
.github/workflows/governed-surface-guard.yml(thepull_requestleg only: its trigger types and its steps; themerge_groupleg, the job id and the check-run name untouched). (stop on breach; explain in the report)Container & model:
S–M,mode:thread(the maintainer raised the lane's concurrency to 6 and the seat container pushes only its own branch),model: default judgement tier(dispatch-gates --tierwith the surface: no path-derived mandate),effort: high. The dispatch order is comment 6425237230 on this card and the thread's opening message points to it (the coordinator channel caps at 2,000 characters). Tier H (.github/workflows/**⇒ the whole PR): draft, the seat's contract review, then the maintainer's hand; ⛔ no seat readies, arms or enqueues it.Clause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card (filing gate ③: the maintainer's 「立卡」 on the director seat's reading; the workflow's own header gives a reason for the trigger the card would drop — the order has the dev measure route 2 first and retire the header's rationale only with the maintainer's hand on the PR)
Thread-read: 6420845946
Premise: holds — on
origin/main:.github/workflows/governed-surface-guard.ymltypes: [opened, synchronize, reopened, ready_for_review](oneready_for_reviewline); thepull_requestleg checks out the full tree and runspnpm installbefore a 1 s check;scripts/check-required-contexts.mjs~431 namesgoverned-surface-guard.yml→ jobgoverned-surface-guard, check-run nameGoverned Surface Queue Guard.Serial constraints cleared: no open PR touches
.github/workflows/governed-surface-guard.yml(scan of the 33 open PRs' file lists at claim time); PR #26230 (the guard script) merged before this claim and touched no workflow file.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 26201, "status": "done", "branch": "claude/project-thread-vqr0lq", "pr": "https://github.com/objectstack-ai/objectstack/pull/26311", "session": "session_01Cpku5P4EYCqaaEGHuMcxik", "premise_still_valid": true, "summary": "Route 2, measured. The governed-surface guard's pull_request leg now checks out full history blobless (filter blob:none) on a sparse tree of scripts/ and .github/, and acquires the generator toolchain only when the new --toolchain-needed answer says the diff touches a register path the lift recomputes (one predicate, toolchainNeededFor, shared with liftGeneratedExceptions). The merge_group leg, the trigger types (ready_for_review kept, the header's reason intact and extended), the job id and the check-run name are unchanged. Two dispatch hypotheses needed a qualifier: the leg is not API-only (its judgment reads git commits and trees, so fetch-depth 0 stays and only the blobs go), and the install is gated rather than dropped (the header's forecast-fidelity reason for installing on both legs holds). The PR's own guard run measured 19 s wall (job 121820715758, 20:12:44Z to 20:13:03Z: checkout 10 s, node 3 s, install steps skipped), against about 94 s on PR 25965; the PR body was written before that run and says NOT MEASURED there, so the seat folds this reading into its review record.", "tests": "node scripts/pm/check-governed-queue-guard.mjs --self-test on 8cd23c77: exit 0, 393 cases pass, including a new 20-case battery (roster floor 28 to 29) that pins the predicate, the lift spy agreeing with it, the answer per event and read state, and the YAML wiring (two checkouts, PR one blobless and sparse, question on the PR leg only, every install step gated on needed not equal to false, restore before install, judgment unconditional). End to end on real PRs with the edited script: PR 25965 judgment byte-identical on a blobless sparse clone vs a full clone, --toolchain-needed false on pull_request and true on merge_group. PR 25813 (two skills references _index.md files): --toolchain-needed true naming both, and after git sparse-checkout disable plus pnpm install the judgment is byte-identical to a full clone with install (same rows LIFTED). Clone costs measured here: blobless full history 7 s / 105 MiB, contents 58 s / 956 MiB, restore 26 s, warm install 13 s. No ablation: workflow and gate-script change, pinned by self-test cases.", "gates": "Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (52 commands, list unchanged after merging origin/main). On final head 8cd23c77 all 52 exit 0, each exit captured before any pipe. Also run outside the derivation: node scripts/check-required-contexts.mjs --self-test (174 assertions, exit 0) and node scripts/pm/check-expected-skips.mjs --self-test (173 cases, exit 0). --ran: 51 derived, 51 run, 0 NOT-MEASURED, 0 UNRUN, a derived zero because every family carried an exit code. pnpm check:pm-dispatch-gates detached: 1899 cases pass, fast tier, 313.4 s; its exit code was read from the verdict line and the absence of ELIFECYCLE, not captured as a status. On the first head it reddened one LIVE pin made false by this change (the CI-measured family set is now the guard's judgment plus its --toolchain-needed invocation); the pin was updated in scripts/pm/dispatch-gates.self-test.mjs. PR check-runs at report time on 8cd23c77: Governed Surface Queue Guard success (19 s), Check Changeset success, filter success, No other open PR may claim the same issue success, No other open PR may claim the same single-writer path success, Part-of PR must not also close its card success; Lint (8 lanes), Type Check (4 lanes), Check Documentation Links, Check PR Size, The card this PR closes must claim this branch in_progress; Test Core 1-8 and Dogfood Regression Gate queued.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on #26201, both reads; no MCP write", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay, request fw-20261026T201221Z-4208e0, run 40393566223: pr_create draft with assignees os-warren and labels skip-changeset, read back 9942/9942 bytes identical; the relay also requested reviewers hotlong and os-zhuang) and this os-dev-report comment through scripts/pm/post-stamped.mjs. git push x3 is not a REST write.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: the guard's own check-run output on a pull request — reproduced on PR 25965 with the main-line script: its single commit's subject names issue 25770, so the pull_request leg attributes the row to 25770 instead of the payload's PR number, reads GET /pulls/25770/reviews (404 on an issue) and prints the review list could NOT be read for a PR that has reviews. enumerateRows prefers pullNumberFromSubject over the fallback, which on the pull_request leg is the event's own PR number; PR 25813 shows the same shape as 4 unattributed governed commits. The early-warning leg always exits 0, so it misleads the reader and refuses nothing. · dedupe: governed queue guard pull_request leg commit subject attribution, pullNumberFromSubject pull_request fallback, review list could NOT be read 404 issue number", "carrier: none · noted, not filed — the cheap leg's run time on a toolchain-needing diff stays roughly where it was (about 26 s restore instead of about 48 s full checkout, plus the install); recorded in the PR's Acceptance notes" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim amendment ·
domain:skills#2· 2026-10-26T20:36ZAmends claim 6425299365 on this card (2026-10-26T19:25Z). The independent contract review on PR #26311 (comment 6426695557,
Served-tier: CONTRACT_REVIEW_TIER,VERDICT: PASSon8cd23c77) read a claim-surface breach and judged it the right minimal route; the seat accepts it as a deviation, not a stop, and amends the claim's surface in the same round.File surface:
.github/workflows/governed-surface-guard.yml(thepull_requestleg only: its trigger types and its steps; themerge_groupleg, the job id and the check-run name untouched) +scripts/pm/check-governed-queue-guard.mjs(the--toolchain-neededmode,readEventDiffextracted frommain(),toolchainNeededForshared withliftGeneratedExceptions, a 20-case battery; no refusal message or exit code on the judgment path changed) +scripts/pm/dispatch-gates.self-test.mjs(one LIVE pin: the CI-measured family set is now the guard's two payload-reading invocations).Why the widening stands: route 2 with the header's forecast-fidelity rationale kept needs the install decided by the one predicate the lift consults. The two alternatives inside the claimed surface were an unconditional install drop (a false alarm on every register-path diff) or a path list restated in YAML (the guard forbids restated lists). The dev did not stop on breach; the PR body names both files with their evidence (its file section, the two script entries), which is the PR-side half of the record; this comment is the claim-side half.
Every other claim field is unchanged: Branch
claude/project-thread-vqr0lq, Tier H,Clause-②: no,Metadata-protocol: none, Thread-read 6420845946.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
ACCEPT → PR #26311 comment 6426788546 ·
domain:skills#2· 2026-10-26T20:39ZContract review (
CONTRACT_REVIEW_TIER, PASS on8cd23c77) → PR #26311 comment 6426695557. Claim amended for the widened surface → comment 6426749756 on this card. Acceptance arm 2 measured: 19 s wall on the PR's own guard run (job 121820715758). Class-(a) finding from the report filed as #26316. Tier H: the PR stays draft and waits for the maintainer's hand (or an authorized APPROVED review / enqueue), after which the seat lands it.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed ·
domain:skills#2·session_01LqKsGyMNFwwnbF5FTAqyPn· 2026-10-26T23:06Zmerged: trueat 2026-10-26T23:05:38Z, merge commitab4e30ab, merged byos-zhuangorigin/maingit log origin/maincarriesab4e30ab(two commits underf60f4ea3);.github/workflows/governed-surface-guard.ymlonmainreadsfilter: blob:none+sparse-checkouton thepull_requestcheckout, the--toolchain-neededquestion step and thegit sparse-checkout disablerestore stepsuccess(2026-10-26T22:46Z → 2026-10-26T22:48Z): the untouchedmerge_groupleg installed and judged as before; nogh-readonly-queue/main/pr-26311-*ref remainsLanding path: Tier H (
.github/workflows/**). Review record =os-zhuang's APPROVED review 5660120134 on head8cd23c77at 2026-10-26T22:46:21Z (an authorizedGOVERNED_APPROVERSaccount), who then readied and enqueued the PR by hand — the maintainer's hand, not the seat's. Beneath it: the independent## Contract review(PR comment 6426695557,CONTRACT_REVIEW_TIER, PASS on8cd23c77) and the seat's ACCEPT (6426788546), posted before the review request was answered, so H69 reads this PR CLEAN (record 20:39Z, head committer 20:01Z).State: the card closed as
completedthroughFixes #26201;pm:dispatchedremoved in this act (the domain, area, type and priority labels stay). The body of #26311 closes no other card. The rule is live: the guard'spull_requestleg checks out commits and trees only and installs the toolchain on demand (19 s wall measured on this PR against about 94 s before); themerge_groupleg, the trigger types, the job id and the check-run name are unchanged.Acceptance: arm 2 (under 20 s) met on the measured run; arm 1 (approve + ready starts no new run) not met by design — route 2 kept
ready_for_review, whose rationale the workflow header keeps; the ready flip byos-zhuangat 2026-10-26T22:46Z re-fired the now-cheap leg before the enqueue. The admin-merge window is now that run's wall time.Filed from this round: #26316 (the
pull_requestleg attributes rows by commit subject and reads an issue number's/reviews). Carrier, not filed — 收班简报 items: the os-dev-report template/prose mismatch (deviations,files_changed); the cone-mode default on the sparse checkout is not pinned (a latersparse-checkout-cone-mode: falsedegrades fail-closed, never lifts); the #26230 enqueue-limb reading stays unmeasured — this landing cleared the approval leg first.Generated by Claude Code