Skip to content

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

Closed

Description

@objectstack-fleet

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-zhuang APPROVED → 15:12:32Z ready_for_review by the same account → 15:12:35Z workflow run (event pull_request) started on head e7b12fdd → 15:14:09Z completed; 15:13:36Z auto_merge_enabled by objectstack-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_request legs 67–111 s each, merge_group legs 76–104 s.

Governing text

  • .github/workflows/governed-surface-guard.yml on: — pull_request: types: [opened, synchronize, reopened, ready_for_review] and merge_group; the header says the pull_request leg "is an EARLY WARNING that deliberately exits 0" and the merge_group leg "is the leg that REFUSES".
  • scripts/check-required-contexts.mjs REQUIRED_CONTEXTS names governed-surface-guard.yml → job governed-surface-guard as a required context; the check-run name Governed Surface Queue Guard is 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_review is 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/synchronize leg already published on that sha stays attached: re-read GET /repos/objectstack-ai/objectstack/commits/e7b12fdd/check-runs and confirm one Governed Surface Queue Guard run existed before 15:12:35Z.
  • P3 — the pull_request leg needs the checkout only to run scripts/pm/check-governed-queue-guard.mjs and 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

  1. Drop ready_for_review from the pull_request trigger types (the ready flip changes no sha; the existing check run stands). Keep opened, synchronize, reopened and the merge_group leg exactly as they are. Add one sentence to the header saying why the type is absent.
  2. If P3 shows the pull_request leg reads nothing from the tree that the API cannot give, make that leg API-only (no checkout, no pnpm install): a node script fetched from the default branch or vendored, or actions/github-script, publishing the same check-run name. The merge_group leg keeps the full checkout (it is the refusing leg and must read the queue head).
  3. scripts/check-required-contexts.mjs --self-test and pnpm check:required-contexts exit 0; the check-run name is unchanged; check:ci-filter-parity, check-expected-skips, check-step-collectors follow.

⛔ 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_request leg'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 Guard remains 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

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • p3, as filed. The governed-surface guard (the governed register is domain:skills) re-runs on ready_for_review and 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

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch order · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-26T19:22Z

    You are the os-dev developer agent for exactly ONE card: #26201, in your own card thread. Your role definition is .claude/agents/os-dev.md in 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-root AGENTS.md before your first edit (Prime Directive 14 and Multi-agent discipline §7 on check-run names), and pm-dispatch references/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 its Branch: line. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: 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. Worktree objectstack-issue-26201 on that branch, based on a fresh origin/main; push with git 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"], labels with skip-changeset); comments, labels and bodies only through scripts/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_writes with endpoints, mcp_calls with 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_request leg of .github/workflows/governed-surface-guard.yml only; ⛔ not the merge_group leg's semantics, ⛔ not the required set, ⛔ no other workflow; the check-run name Governed Surface Queue Guard is contract on both legs.

    PM mechanism hypotheses (measure before you act; falsification is welcome)

    Read on origin/main at 2026-10-26T19:22Z:

    1. Conflict to resolve by measurement, not by choosing a side blind. The workflow's own header (lines ~33–38) gives a deliberate reason for ready_for_review in types:: 「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 the pull_request leg's wall time under 20 s) is satisfied by route 2 alone. So: implement route 2 first — make the pull_request leg API-only (no checkout of the full tree, no pnpm install) while keeping ready_for_review and 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).
    2. P3 measured: read the step 「Governed surfaces may not enter the merge queue unreviewed」 and scripts/pm/check-governed-queue-guard.mjs's pull_request path — list what it reads from the tree (the register in check-governed-merges.mjs, GOVERNED_APPROVERS, CODEOWNERS mirror) versus the API. A sparse checkout of scripts/pm/** + .github/CODEOWNERS (actions/checkout with sparse-checkout, no submodules, depth 1) and plain node with no pnpm install is the cheapest route if the script's imports are dependency-free on that path — measure the import graph (node --check is 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.
    3. P2: the ready flip changes no sha, so a run already published on the head stays; with route 2 the re-fired run is cheap and the admin-merge window shrinks to the run's wall time — quote it from THIS PR's own run in the PR body (acceptance 2).
    4. Gates: 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-collectors as dispatch-gates --commands derives them for .github/workflows/**; job id governed-surface-guard and the check-run name unchanged (required-contexts row at scripts/check-required-contexts.mjs ~431).

    Card-specific clauses

    • Tier H (.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-②: no and Metadata-protocol: none go line-initial into the PR body after the first line Fixes #26201. skip-changeset: nothing published ships from .github/workflows/**.
    • Gates: derive with 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 & then tail --pid); heavy commands through bash scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-26201.
    • Commit trailers: the model-free pair exactly as os-dev.md / AGENTS.md spell them; ⛔ no model name anywhere in the commit or PR.
    • Hand-back: post the os-dev-report JSON as a comment on this card (first line os-dev-report) through post-stamped.mjs --comment=26201, then send the seat thread 「Dispatch the skills lane」 one message: PR number, head sha, and the report's status. Keep your thread open for a patch round. ⛔ Do not wait for CI after the PR opens.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (fifth wave) · 2026-10-26T19:25Z
    Session: session_01LqKsGyMNFwwnbF5FTAqyPn (seat; implementation by card thread session session_01Cpku5P4EYCqaaEGHuMcxik, mode:thread)
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-vqr0lq (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
    Worktree: objectstack-issue-26201 in the card thread's clone
    Domain: domain:skills
    Seat: domain:skills#2 (seat post #19287)
    File surface: .github/workflows/governed-surface-guard.yml (the pull_request leg only: its trigger types and its steps; the merge_group leg, 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 --tier with 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.yml types: [opened, synchronize, reopened, ready_for_review] (one ready_for_review line); the pull_request leg checks out the full tree and runs pnpm install before a 1 s check; scripts/check-required-contexts.mjs ~431 names governed-surface-guard.yml → job governed-surface-guard, check-run name Governed 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

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment · domain:skills#2 · 2026-10-26T20:36Z

    Amends claim 6425299365 on this card (2026-10-26T19:25Z). The independent contract review on PR #26311 (comment 6426695557, Served-tier: CONTRACT_REVIEW_TIER, VERDICT: PASS on 8cd23c77) 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 (the pull_request leg only: its trigger types and its steps; the merge_group leg, the job id and the check-run name untouched) + scripts/pm/check-governed-queue-guard.mjs (the --toolchain-needed mode, readEventDiff extracted from main(), toolchainNeededFor shared with liftGeneratedExceptions, 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

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #26311 comment 6426788546 · domain:skills#2 · 2026-10-26T20:39Z
    Contract review (CONTRACT_REVIEW_TIER, PASS on 8cd23c77) → 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

  7. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-26T23:06Z

    probe reading
    PR #26311 merged: true at 2026-10-26T23:05:38Z, merge commit ab4e30ab, merged by os-zhuang
    origin/main git log origin/main carries ab4e30ab (two commits under f60f4ea3); .github/workflows/governed-surface-guard.yml on main reads filter: blob:none + sparse-checkout on the pull_request checkout, the --toolchain-needed question step and the git sparse-checkout disable restore step
    merge queue the queue-leg run of the NEW workflow on this PR's own queue group, run 40415004542, concluded success (2026-10-26T22:46Z → 2026-10-26T22:48Z): the untouched merge_group leg installed and judged as before; no gh-readonly-queue/main/pr-26311-* ref remains

    Landing path: Tier H (.github/workflows/**). Review record = os-zhuang's APPROVED review 5660120134 on head 8cd23c77 at 2026-10-26T22:46:21Z (an authorized GOVERNED_APPROVERS account), 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 on 8cd23c77) 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 completed through Fixes #26201; pm:dispatched removed 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's pull_request leg checks out commits and trees only and installs the toolchain on demand (19 s wall measured on this PR against about 94 s before); the merge_group leg, 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 by os-zhuang at 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_request leg 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 later sparse-checkout-cone-mode: false degrades fail-closed, never lifts); the #26230 enqueue-limb reading stays unmeasured — this landing cleared the approval leg first.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions