Repository navigation
feat(pm/fleet-write): a relay op that re-runs only the failed jobs of one CI run, once, fenced like workflow_dispatch (Tier H) #25169
Description
Filing gate ③: a task the maintainer directed, verbatim: 「立卡:给中转加一个只做「重跑失败任务」的操作」.
- Who acts: the
domain:devxlane, which ownsscripts/pm/fleet-write/**. The maintainer merges it:.github/workflows/fleet-write.ymlis governed (check-governed-merges --testsays GOVERNED, Tier H). - Dedupe: none found ("rerun-failed-jobs relay", "fleet-write re-run").
Why
The relay (scripts/pm/fleet-write/ops.mjs) refuses run cancel, re-run and delete by construction (ops.mjs:33). Its token would otherwise need actions: write, and that grant also reaches cancelling and deleting runs, the cache, artifacts, secrets and variables (ops.mjs:124). So no seat can re-run a CI job. When a required check fails in a way that looks like a registry or network flake, the only path left is a human click, or a write as a personal account, which the seat rules forbid.
The measured case: #25155's Test Core (3/6) failed twice in @objectstack/create-objectstack, at 02:30Z and 02:52Z on 2026-10-22. There was no vitest assertion annotation, and an npm HTTP 429 was logged in the same window. The tree passes locally on the exact merge ref. Run 39713789453 needed the maintainer to press "Re-run failed jobs".
What to build: one row, fenced the way workflow_dispatch is (ops.mjs ~118-135)
- Op:
run_rerun_failed, callingPOST /repos/{repo}/actions/runs/{run_id}/rerun-failed-jobs. - Fences:
- Workflow allowlist. Accept the run only if its
pathis in a closed list. Start with.github/workflows/ci.yml. A workflow joins only through a PR that adds it, never at request time. - Pull-request runs only. The run's
eventmust bepull_request. Refusepush,merge_group, release,scheduleandworkflow_dispatchruns. - Once. Refuse unless the run's
run_attemptis 1. That enforces the line rule "re-run at most once; a second failure is real" in code. - Failed runs only. The run must be
completedwithconclusionfailure. - Alone in its stroke. The action is the only one in its dispatch, so the wider token serves one request and nothing else.
- A token minted only for this stroke. The validator sets
permission_actions=writefor this op alone, and every other stroke's token stays as it is today.
- Workflow allowlist. Accept the run only if its
- Read-back. After dispatch, confirm
run_attemptbecame 2 and the failed jobs arequeuedorin_progress. Report the new attempt's job ids. REFUSED_PATH_FAMILIESstill refuses every otheractions/*path. Make the self-test prove that run cancel and delete, cache, artifacts, secrets and variables all stay refused, and thatrerun(the whole run) andjobs/{id}/rerunstay refused too.- Wrapper. A small
scripts/pm/rerun-failed.mjs --run ID, in the style oflabel-write.mjs, that refuses before dispatch when a fence fails and names which one. - Docs. Add a line to the pm-dispatch skill: a seat may re-run a PR's failed CI jobs once through this op, only after it has written down why it judges the failure a flake. A second failure is real.
Proof
validate.mjs --self-testanddispatch.mjs --self-testare extended, with a case for each fence on both sides.- One real use on a PR run, read back.
check-governed-merges --branchreads GOVERNED. The PR needs the maintainer's approval.
Activity
objectstack-fleet commentedon Oct 22, 2026
Claim: PM loop round 41 · a p2
domain:devxcard the maintainer directed (「立卡:给中转加一个只做「重跑失败任务」的操作」); serial dispatch under the maintainer's 「串行派发」Session:
session_01FM29ZqLTrnPNXxijioZwMpAccount:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25169-relay-rerun-failedWorktree:
objectstack-issue-25169Domain:
domain:devxSeat:
domain:devx#1File surface:
scripts/pm/fleet-write/ops.mjs: one row,run_rerun_failed, with the card's six fences.scripts/pm/fleet-write/validate.mjs,dispatch.mjsandexecute.mjs: the stroke-scopedactions: writegrant for this op alone, the read-back, and the self-test cases for each fence on both sides.scripts/pm/rerun-failed.mjs: new, the wrapper..github/workflows/fleet-write.yml: only if the existing stroke-scopedpermission-actionsinput cannot carry this op as it carriesworkflow_dispatch. This is Tier H..claude/skills/pm-dispatch/SKILL.mdor its reference: the one usage line the card names. This is Tier S.actions/*path, no product code, no changeset text.Container & model:
M (one relay op with six fences and their self-tests, a wrapper, one doc line; governed),mode:subagent,model: TIER_DEFAULTClause-②: no
Responsibility:
no seat can re-run a PR's failed CI job: the relay refuses run re-runs by construction, so a flake needs the maintainer's click (#25155's run 39713789453) (#25169)|none: the relay's whitelist is deliberate, and this card widens it by one fenced row|every seat driving a PR to green, and the maintainer, who stops being the re-run buttonThread-read: none
Serial constraints cleared: no open PR touches
scripts/pm/fleet-write/**,.github/workflows/fleet-write.ymlor.claude/skills/pm-dispatch/**, and no liveClaim:on apm:dispatchedcard names them (read at 2026-10-22T06:37Z). PR feat(pm): the relay requests review from every GOVERNED_APPROVERS account on a Tier H pull; CODEOWNERS routes every Tier H surface to both approvers #25064, the last relay change, has merged. No dev of this session is in flight: [finding] main's lockfile matches two new OSV advisories on @vitest/mocker 4.1.11 (GHSA-qxg6-89mg-rqv8, GHSA-xp26-3454-mjgp; fixed in 4.1.12): the scheduled OSV scan is red again #25163 landed as PR fix(deps): re-lock vitest and the @vitest/* family onto 4.1.12 for the two @vitest/mocker OSV advisories #25164.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25169,
"status": "done",
"branch": "claude/issue-25169-relay-rerun-failed",
"pr": "#25186",
"session": "session_01FM29ZqLTrnPNXxijioZwMp — subagent run: the dispatching seat's id, which the relay envelope also carried (derived from the container's CLAUDE_CODE_REMOTE_SESSION_ID)",
"premise_still_valid": true,
"summary": "Added one fenced relay op, run_rerun_failed (scripts/pm/fleet-write/ops.mjs): ONE bodiless POST /repos/{repo}/actions/runs/{run_id}/rerun-failed-jobs, payload run_id alone, alone in its stroke, actions write minted for that stroke through the EXISTING stroke-scoped permission_actions output, so .github/workflows/fleet-write.yml is untouched (ruling 3). Its four run fences (path in the closed RERUN_FAILED_WORKFLOWS = [.github/workflows/ci.yml], event pull_request, run_attempt 1, completed+failure) are judged authoritatively by the executor on the run read immediately before the POST, so a fence that fails sends nothing (exit 5, row not sent). The 201 lands only when the run reads back as attempt 2 with attempt 1's failed jobs queued or in_progress, and the row names their new job ids. REFUSED_PATH_FAMILIES carves out exactly runs/{digits}/rerun-failed-jobs, and every other actions path stays refused and pinned. The new relay-only wrapper scripts/pm/rerun-failed.mjs makes the advisory fence check, sends ONE stroke through sendFleetWrite and reads attempt 2's job ids back. dispatch.mjs's no-run conformance learns a NAMED relay-only sender whose control refuses under direct instead of writing. The usage line lives in references/platform-readings.md, paid for by deleting the dated MCP re-run reading that duplicates an earlier line. Governed tier: Tier S, not the card's Tier H, because the workflow was not edited. The dispatch says the maintainer approves and merges, and the PR body says so; per the register a Tier S PR lands on a seat's contract review, so that conflict is the PM's to settle (stated here, not picked). Premise: the relay's refusal by construction held. The card's 'no seat can re-run' is partly contradicted by pm-dispatch SKILL.md's one MCP write exception, rerun_failed_jobs, as the personal identity (open question 1). No real use was made: the relay runs main's code.",
"tests": "All taken at f2ee5ad (final head). node scripts/pm/fleet-write/validate.mjs --self-test: exit 0, '190 cases pass across 12 batteries' (new run_rerun_failed battery 30 cases, floor 11 to 12). node scripts/pm/fleet-write/execute.mjs --self-test: exit 0, '184 cases pass across 19 batteries' (new battery 17 cases, floor 18 to 19; each of the four run fences refused at execute time with ZERO writes and accepted on the measured run 39713789453 shape; attempt 2 never seen is FAILED UNCONFIRMED after the 60 s window). node scripts/pm/fleet-write/dispatch.mjs --self-test: exit 0, '247 cases pass across 22 batteries' (new seat battery 6 cases, floor 21 to 22; the conformance drives rerun-failed.mjs and pins the relay-only roster by name). node scripts/pm/rerun-failed.mjs --self-test: exit 0, '28 cases pass across 5 batteries'. One fixture fix was found and made during development: the execute happy-path case first used the shared pace log, which pushed write-pace's hourly budget over by one and refused a later annotations case with exit 10 (measured); it now uses its own pace log. Ablation: none, because the diff is scripts and one doc line (role file: scripts are not ablated). No package is touched, so there is no build closure or typecheck.",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands derived 51 families from the six changed paths at f2ee5ad. All 51 ran with exit 0 captured before any pipe; check:doc-formula-expressions first exited 3 PREREQUISITE NOT MET and passed after its declared build (turbo build of @objectstack/formula and @objectstack/lint under os-verify-lock, VERDICT command-exit 0). pnpm check:pm-dispatch-gates ran detached with nohup and tail --pid: EXIT_CODE=0, '1913 cases pass; 6 slow section(s) deferred'. --ran: '51 derived famil(ies) accounted for — 51 run, 0 NOT-MEASURED (a DERIVED zero — all 51 recorded an exit code and none of them is 3)'; rerun-failed --self-test recorded as outside the derivation. check-skill-line-ratchet: platform-readings.md 460 lines (ceiling 460), no 120-byte overrun. check-governed-merges --branch: GOVERNED, Tier S (.claude/** x1), 1285 changed lines. git merge-tree against origin/main was clean, then origin/main was merged (two disjoint commits) and the union re-run on that head. CI check-runs at report time (non-skipped): Auto Label=success; Dogfood Regression Gate=success; Test Core (6/6)=in_progress; Test Core (5/6)=in_progress; Test Core (4/6)=in_progress; Test Core (3/6)=in_progress; Test Core (1/6)=in_progress; Test Core (2/6)=in_progress; No other open PR may claim the same single-writer path=success; The card this PR closes must claim this branch=success; Check Documentation Links=success; Type Check · consumer gates=in_progress; Type Check · workspace=success; Lint · scoped · slot lookup & corpus=in_progress; Lint · ESLint=in_progress; Lint · docs & code guards=in_progress; Lint · scoped · query-options erasure=in_progress; Type Check · source gates=in_progress; Type Check · debt ledger=success; Lint · repo & tooling gates=in_progress; Lint · scoped · tooling self-tests=in_progress; Lint · CI, release & engine gates=in_progress; Check Changeset=success; Lint · PM dispatch-gates=in_progress; Part-of PR must not also close its card=success; filter=success; Check PR Size=success; No other open PR may claim the same issue=success; Governed Surface Queue Guard=success. The rest are in_progress, honestly; the required set's verdict is the PM's to read.",
"mcp_calls": "0",
"api_writes": "2 — both POST /repos/objectstack-ai/objectstack/dispatches through scripts/pm (the fleet relay, as objectstack-fleet[bot]): (1) the pr_create stroke via fleet-write/dispatch.mjs (relay run 39742805225, conclusion success: POST /repos/objectstack-ai/objectstack/pulls draft, POST /issues/25186/assignees os-warren, POST /issues/25186/labels skip-changeset+tooling; body read back 12942 bytes identical, labels and assignee read back on the PR); (2) this os-dev-report comment via scripts/pm/post-stamped.mjs (POST /issues/25169/comments). git push is not a REST write: 6 pushes (the empty-branch probe, then 5).",
"open_questions": [
{
"question": "pm-dispatch SKILL.md's invariant still names MCP rerun_failed_jobs as the one MCP write exception (seat's personal identity, prose-only fence). With run_rerun_failed there are now two seat re-run paths.",
"options": [
"A retire the MCP exception once the relay op's first real use reads back, leaving one path (fleet identity, fences in code); a Tier S SKILL.md edit",
"B keep both paths"
],
"recommendation": "A. Business need: the measured flake case needs one path. Long-term soundness: fences in code beat a prose rule. Guarding AI against mistakes: one path is harder to get wrong. Startup focus: retire immediately, with no dual path."
},
{
"question": "The six ruled fences do not compare the run's head_sha with the pull's current head. platform-readings records that both repos' CI concurrency groups are keyed by PR number, so re-running a stale head's run cancels the current head's run. Should a seventh fence refuse it?",
"options": [
"A a follow-up card adding a head fence in the relay (refuse unless the run's head_sha is the open pull's head)",
"B keep the wrapper's advisory print of head sha and pull number"
],
"recommendation": "A. It is a narrowing that closes a measured AI-mistake path, and it adds no new capability; the ruling's six fences were executed as ruled."
},
{
"question": "Ruling 5 says the previously failed jobs are queued or in_progress. Measured on run 39713789453, attempt 2 lists a dependent failed job (the Test Core aggregate) only when its needs finish, 16 minutes later. As built, a failed job not listed yet is NAMED on the row, not failed; every listed one must be queued or in_progress, and at least one must be.",
"options": [
"A accept as built",
"B require every attempt-1 failure queued or in_progress at once"
],
"recommendation": "A. B would FAIL every real use on ci.yml, because its aggregate job always needs the shards."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — .github/workflows/fleet-write.yml's comments (header, validate step, permission-actions input) still call workflow_dispatch the ONE consumer of actions write and say the op table has no re-run row; ruling 3 kept the workflow out of this PR, so that prose is stale; a Tier H comment-only edit · dedupe: fleet-write.yml actions write comment, permission-actions consumer, workflow comment drift",
"carrier: none · noted, not filed — scripts/pm/rerun-failed.mjs --self-test is run by no CI job; wiring it needs a root package.json alias plus a lint.yml step, both outside the card's file surface; CI drives its relayMissProbe through check:pm-fleet-write-dispatch · dedupe: rerun-failed self-test CI, check:pm-rerun-failed, self-test unwired",
"carrier: none · noted, not filed — .claude/settings.json has no allow rule for node scripts/pm/rerun-failed.mjs or its --use-env-proxy form, while every sibling write tool has both; a seat's first use may meet a permission prompt (not measured) · dedupe: settings allow rerun-failed, permissions allow pm tool",
"carrier: none · noted, not filed — RERUN_FAILED_WORKFLOWS is a list of paths, as the card spells it, so it admits .github/workflows/ci.yml in any fleet repository the envelope names (objectui has one); the sender gate still requires write there · dedupe: rerun allowlist per repo, ci.yml objectui rerun",
"carrier: none · noted, not filed — the stale-head re-run hazard is outside the six ruled fences; the wrapper prints head sha and pull number (open question 2) · dedupe: rerun stale head cancels, concurrency group PR number, head fence"
]
}
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25169,
"status": "done",
"branch": "claude/issue-25169-relay-rerun-failed",
"pr": "#25186",
"session": "session_01FM29ZqLTrnPNXxijioZwMp — subagent run (resume after a container restart): the dispatching seat's id",
"premise_still_valid": true,
"summary": "Resume run, verify only: no new commit and no push. The first run built PR 25186 at f2ee5ad in five commits: c6db69f (the row, its fences and the validator battery), fc72728 (the executor's fence and read-back), 847734a (the relay-only wrapper scripts/pm/rerun-failed.mjs), 3a8aecd (the usage line in references/platform-readings.md) and f2ee5ad (a merge of origin/main). The op is run_rerun_failed: ONE bodiless POST /repos/{repo}/actions/runs/{run_id}/rerun-failed-jobs, with run_id as its only payload key, alone in its stroke. actions write is minted through the existing stroke-scoped permission_actions output, so .github/workflows/fleet-write.yml is untouched. The executor judges the four run fences authoritatively, on the run GET that is the last request before the POST, and the 201 counts as landed only on an attempt-2 read-back that names the new job ids. Re-verified here: the worktree is clean; HEAD, the remote branch and the PR head are all f2ee5ad; the six-file diff stays inside the claim's surface; and the live PR body is byte-identical (12943 bytes) to the first run's stored copy. Checked against rulings 1-7, no gap needs a commit. Two disclosed readings stand. Ruling 5: a failed job that attempt 2 does not list yet is named, not failed (measured on run 39713789453). Ruling 6: the line spells rerun-failed.mjs bare, because scripts/pm/rerun-failed.mjs would make it 130 bytes against the file's 120-byte line budget (it measures 119 as landed). Ruling 7 has one literal-reading note (open question 4). The PR body needs no change. Premise correction: the resume prompt said no os-dev-report was on the card, but the first run posted comment 6335627986 at 07:50:06Z (relay run 39743221867, 10270 bytes read back identical); only its final handback was lost. This comment is posted because the resume dispatch ordered one covering the re-verification, and it supersedes 6335627986. Governed tier: Tier S (.claude/** x1). The dispatch's 'the maintainer approves and merges' still conflicts with the register's seat landing on a contract review; that is the PM's to settle, stated here, not picked. Cleanup: once this comment posts, node_modules and the worktree are removed with git worktree remove, without --force.",
"tests": "All at f2ee5ad (git rev-parse --short HEAD; this run made no commit). Each exit code was captured by redirect-then-capture, before any pipe. node scripts/pm/fleet-write/validate.mjs --self-test: exit 0, '190 cases pass across 12 batteries' (floor 12; the run_rerun_failed battery is pinned at 30). node scripts/pm/fleet-write/dispatch.mjs --self-test: exit 0, '247 cases pass across 22 batteries' (floor 22; the seat battery is pinned at 6; the conformance drives rerun-failed.mjs's relayMissProbe and pins the relay-only roster by name). node scripts/pm/fleet-write/execute.mjs --self-test: exit 0, '184 cases pass across 19 batteries' (floor 19; the run_rerun_failed battery is pinned at 17). node scripts/pm/rerun-failed.mjs --self-test: exit 0, '28 cases pass across 5 batteries' (floor 5). Every count matches the first run's. Ablation: none, because the diff is scripts and one doc line. No package is touched, so there is no build closure and no package test or typecheck is owed.",
"gates": "Local, at f2ee5ad. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: exit 0. Its first line names objectstack-ai/objectstack at f2ee5ad, and the change set is 6 paths vs merge base 6251f59 (1285 changed lines, under 3000). It derived 51 commands, the same 51 the first run derived. All 51 ran. 50 ran in the foreground, each exit 0. pnpm check:pm-dispatch-gates ran detached under nohup and was waited out: EXIT_CODE=0, 'dispatch-gates self-test (fast tier): 1913 cases pass; 6 slow section(s) deferred' (290.5s). check:pm-skill-ratchet's last line: 'declared cross-file moves: 2, total ceilings down 486 lines'. check:nul-bytes, check:doc-formula-expressions, check:pm-governed-merges, check:pm-write-pace and check:entry-guard: exit 0. --ran: my first call put --commands beside --ran and was refused as usage (exit 2, no verdict, not a measurement). The re-run as dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE gave exit 0, 'Run reconciliation — 51 derived, 51 run, 0 NOT-MEASURED, 0 UNRUN', with rerun-failed --self-test recorded as outside the derivation. node scripts/pm/check-governed-merges.mjs --pr 25186: exit 3, which its header defines as 'the given paths ARE governed'. Verdict 'GOVERNED — Tier S': .claude/** x1 (references/platform-readings.md), 1 of 6 paths on the register, 1285 changed lines under 3000. git fetch origin main, then git merge-tree --write-tree origin/main HEAD against origin/main f7d7403: exit 0, a tree id alone (eadc7124a9), no conflict. Main moved by one commit (f7d7403: packages/platform-objects translations with its changeset and test), disjoint from the 6 paths, so no merge was made (AGENTS.md multi-agent section 10: no conflict and no main-caused red). CI check-runs on f2ee5ad, all completed and none red. Required: Lint & Repo Gates=success; TypeScript Type Check=success; Test Core=success; Build Core=skipped (path filter); Governed Surface Queue Guard=success. Test Core 1/6 to 6/6=success each. Type Check workspace, source gates, consumer gates and debt ledger=success. Lint ESLint, docs & code guards, repo & tooling gates, CI release & engine gates, PM dispatch-gates, scoped tooling self-tests, scoped slot lookup & corpus and scoped query-options erasure=success. Check Changeset=success; Check PR Size=success; Check Documentation Links=success; Dogfood Regression Gate=success; Auto Label=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; The card this PR closes must claim this branch=success; Part-of PR must not also close its card=success. The rest were skipped: opt-in harnesses, Console Pin Gate, Temporal Conformance, Dogfood shards, Build Docs.",
"mcp_calls": "5 — all read tools: mcp__github__pull_request_read x3 (get, get_files, get_check_runs) and mcp__github__issue_read x2 (get, get_comments). No write tool.",
"api_writes": "1 — this os-dev-report comment, via scripts/pm/post-stamped.mjs through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches; the relay run sends POST /repos//issues/25169/comments). No git push this run, since no fix was needed. Reads outside MCP: one gh api GET /repos/objectstack-ai/objectstack/pulls/25186 for the body byte check, after scripts/pm/with-fleet.sh --read refused in this shell (exit 3, nothing ran). For the whole-run account, the first run's writes were the pr_create stroke (relay run 39742805225), comment 6335627986 (relay run 39743221867) and 6 git pushes.",
"open_questions": [
{
"question": "pm-dispatch SKILL.md's invariant still names MCP rerun_failed_jobs as the one MCP write exception (the seat's personal identity, fenced only in prose). With run_rerun_failed, a seat now has two re-run paths.",
"options": [
"A retire the MCP exception once the relay op's first real use reads back, leaving one path (the fleet identity, fences in code); a Tier S SKILL.md edit",
"B keep both paths"
],
"recommendation": "A. Business need: the measured flake case needs one path. Long-term soundness: fences in code beat a prose rule. Guarding against AI mistakes: one path is harder to get wrong. Startup focus: retire it immediately, with no dual path."
},
{
"question": "The six ruled fences do not compare the run's head_sha with the pull's current head. platform-readings records that both repositories' CI concurrency groups are keyed by PR number, so re-running a stale head's run cancels the current head's run. Should a seventh fence refuse that?",
"options": [
"A a follow-up card adding a head fence in the relay (refuse unless the run's head_sha is the open pull's head)",
"B keep the wrapper's advisory print of head sha and pull number"
],
"recommendation": "A. It is a narrowing that closes a measured AI-mistake path and adds no capability; the six fences were executed as ruled."
},
{
"question": "Ruling 5 says the previously failed jobs are queued or in_progress. Measured on run 39713789453, attempt 2 lists a dependent failed job (the Test Core aggregate) only when its needs finish, 16 minutes later. As built, a failed job not listed yet is NAMED on the row, not failed; every listed one must be queued or in_progress, and at least one must be.",
"options": [
"A accept as built",
"B require every attempt-1 failure queued or in_progress at once"
],
"recommendation": "A. B would FAIL every real use on ci.yml, because its aggregate job always needs the shards."
},
{
"question": "Ruling 7, literal reading: dispatch.mjs --self-test gains no case for the four run fences or for the grant. The seat sender never reads the run and never mints the token. Those fences are judged, with cases on both sides, in validate.mjs (pure predicates, plus alone-in-stroke and empty-grant-elsewhere), in execute.mjs (at execute time, with zero writes on refusal) and in rerun-failed.mjs (advisory). dispatch.mjs carries alone (refused and accepted) and no-widening at the packer.",
"options": [
"A accept as built: each fence's cases live where that fence is judged",
"B add mirror cases to dispatch.mjs"
],
"recommendation": "A. Business need: a dispatch.mjs case would assert ops.mjs predicates that dispatch.mjs never calls. Long-term soundness: one assertion per seam, not duplicated ones. Guarding against AI mistakes: a mirror case no dispatch.mjs change can fail is a phantom check. Startup focus: no added surface."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — .github/workflows/fleet-write.yml's comments (header, validate step, permission-actions input) still call workflow_dispatch the ONE consumer of actions write and say the op table has no re-run row. Ruling 3 kept the workflow out of this PR, so that prose is stale; fixing it is a Tier H comment-only edit · dedupe: fleet-write.yml actions write comment, permission-actions consumer, workflow comment drift",
"carrier: none · noted, not filed — no CI job runs scripts/pm/rerun-failed.mjs --self-test. Wiring it needs a root package.json alias plus a lint.yml step, both outside the card's file surface; CI does drive its relayMissProbe through check:pm-fleet-write-dispatch · dedupe: rerun-failed self-test CI, check:pm-rerun-failed, self-test unwired",
"carrier: none · noted, not filed — .claude/settings.json has no allow rule for node scripts/pm/rerun-failed.mjs or its --use-env-proxy form, while every sibling write tool has both, so a seat's first use may meet a permission prompt (not measured) · dedupe: settings allow rerun-failed, permissions allow pm tool",
"carrier: none · noted, not filed — RERUN_FAILED_WORKFLOWS is a list of paths, as the card spells it, so it admits .github/workflows/ci.yml in any fleet repository the envelope names (objectui has one). The sender gate still requires write on that repository · dedupe: rerun allowlist per repo, ci.yml objectui rerun",
"carrier: none · noted, not filed — the stale-head re-run hazard is outside the six ruled fences; the wrapper prints the head sha and pull number (open question 2) · dedupe: rerun stale head cancels, concurrency group PR number, head fence",
"carrier: none · noted, not filed — the usage line replaced the dated 2026-09-13 reading (seat REST jobs/{id}/rerun 403, MCP rerun_failed_jobs 201, lock 1 not forbidding it). Its MCP half survives at platform-readings.md:120 and SKILL.md:92, but the seat REST jobs/{id}/rerun 403 reading now survives nowhere in the file (line 120 reads runs/{id}/rerun-failed-jobs). A reading lost to the ratchet's zero headroom, not a defect · dedupe: jobs rerun 403 reading, platform-readings rerun cluster"
]
}
objectstack-fleet commentedon Oct 22, 2026
File-surface addendum to claim
6334469056·domain:devxseat 1 ·os-warren·session_01FM29ZqLTrnPNXxijioZwMp· 2026-10-22T08:14ZFix round 1 of PR #25186 adds two items to the surface:
package.json. One edit:scripts.check:pm-fleet-write-dispatchalso runsnode scripts/pm/rerun-failed.mjs --self-test.lint.yml:1412already runs that script, so the wrapper's self-test reaches CI with no workflow edit. The file is not on the governed register.head_shais its pull's current head.ci.yml's concurrency group isci-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}withcancel-in-progress: true, so re-running a stale head's run cancels the current head's run. SKILL.md's existing MCP exception already reads 「先核 head」.No open PR touches the root
package.json'scheck:pm-fleet-write-*scripts (read at 2026-10-22T08:14Z). ⛔ Still no workflow and no.claude/settings.jsonedit.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25169,
"status": "done",
"branch": "claude/issue-25169-relay-rerun-failed",
"pr": "#25186",
"session": "session_01FM29ZqLTrnPNXxijioZwMp — subagent run (fix round 1): the dispatching seat's id",
"premise_still_valid": true,
"summary": "Fix round 1, two new commits on f2ee5ad, pushed as a fast-forward to 49804f2 with no amend, rebase or force-push. 120ca3c adds the seventh fence, head, as ruled on Q2. RERUN_FAILED_FENCES gains a head fence, every fence predicate now takes (run, pull), and rerunFailedPullNumber reads the run's pull_requests[0].number. The executor reads the run and then that pull's own GET /repos/{repo}/pulls/{n}, the last two requests before the POST, and refuses unless the run's head_sha equals the pull's current head. The run's embedded pull copy is never read. A run naming no pull (pull_requests empty, absent, or an entry with no number) is refused by name with no pull read, and an unreadable pull leaves the fence unjudged. Each of those is zero writes with the POST row not sent. The wrapper judges the same fence earlier as an advisory refusal (exit 2 naming it; exit 3 for an unreadable pull) and re-reads the pull after a failed relay run. 49804f2 appends && node scripts/pm/rerun-failed.mjs --self-test to the root check:pm-fleet-write-dispatch. lint.yml runs that script at its 'Fleet-write dispatcher self-test' step, whose outcome the lane's toJSON(steps) aggregation reads, so no workflow edit was made. Mechanism checked on disk: ci.yml's concurrency group is keyed on github.event.pull_request.number with cancel-in-progress true. The usage line has no room for the head rule (119 of 120 bytes), so the fence alone carries it and platform-readings.md is unchanged. No SKILL.md, lint.yml, fleet-write.yml or .claude/settings.json edit. Q3 and Q4 are accepted as built; Q1 is with the seat for the maintainer. The PR body is not patched: pr_body_replacements below holds 14 exact replace/with pairs against the body as it stands at f2ee5ad (each old string occurs exactly once). Governed tier: Tier S (.claude/** x1); the root package.json is not on the register. The root package is private, so skip-changeset still holds. The worktree was recreated at f2ee5ad for this round; once this comment posts, it is removed again (node_modules first, git worktree remove without --force).",
"tests": "All at 49804f2 (git rev-parse --short HEAD after the last commit). Each exit code was captured by redirect-then-capture, before any pipe. node scripts/pm/fleet-write/validate.mjs --self-test: exit 0, '193 cases pass across 12 batteries'. The run_rerun_failed battery's pin rose 30 to 33, with three new head cases: a stale head, an answer for another pull, a missing head_sha and no pull answer each refused naming the head fence alone; the embedded copy agreeing with head_sha while the pull's own answer moved on, refused; a run naming no pull (empty, absent, no number) refused by name. The measured-run acceptance now requires the pull's current head, and every-fence-in-one-pass now names 5. node scripts/pm/fleet-write/execute.mjs --self-test: exit 0, '188 cases pass across 19 batteries'. Its pin rose 17 to 21. The happy path reads PERM, attempt 1 jobs, the run, the pull, then the POST. Four new cases: a stale head refused with zero writes, the pull read the last request and both heads named; the embedded copy never trusted; a fork run (pull_requests empty or absent) refused with zero writes and NO pull read; an unreadable pull with zero writes. The four older fence refusals now end on the pull read. node scripts/pm/fleet-write/dispatch.mjs --self-test: exit 0, '247 cases pass across 22 batteries', unchanged (Q4 A); its conformance drives the wrapper's probe through the new pull read. node scripts/pm/rerun-failed.mjs --self-test: exit 0, '31 cases pass across 5 batteries'. The seat-fence battery pin rose 8 to 11: a stale head refused (exit 2, no dispatch), a fork run refused by name with no pull read, and an unreadable pull exit 3; accepted reads the run, then the pull, then attempt 1. pnpm check:pm-fleet-write-dispatch now prints both success lines, exit 0. One defect was caught in development: three validator cases first failed (3 of 193) because they passed the fence helper to Array.map, whose index landed in the new pull parameter. The helper was split into a one-argument fenced and a two-argument fencedWith, and the run is green. Ablation: none, because the diff is scripts and one npm script line.",
"gates": "Local, at 49804f2. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: exit 0. Its first line names objectstack-ai/objectstack at 49804f2; the change set is 7 paths vs merge base 6251f59, 1408 changed lines under 3000. It derived 59 commands (51 before; package.json adds check-dts-emitted --self-test, dts-closure, dual-build-cjs-loads, lean-entry-closure, manifest-repository-directory, merge-driver, sourcemap-no-sources-content and turbo-task-graph). pnpm check:pm-dispatch-gates ran detached under nohup and was waited out: EXIT_CODE=0, fast tier, 290.8s. Of the other 58, 53 exited 0 at once. Five exited 3, PREREQUISITE NOT MET, on the fresh worktree, which are not measurements: check:doc-formula-expressions, check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure and check:sourcemap-no-sources-content. Under bash scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=os-dev-25169: turbo build of @objectstack/formula and @objectstack/lint gave 'VERDICT command-exit 0' (4 of 4 cached, FULL TURBO), then doc-formula-expressions exit 0. turbo run build --filter=!@objectstack/docs --concurrency=2 gave 'VERDICT command-exit 0', 76 of 76 tasks, held 323s. Then dts-closure exit 0 ('76 built package(s) swept - 179/179 declared declaration file(s) present'), dual-build-cjs-loads exit 0, lean-entry-closure exit 0 ('Admitted set held exactly (15 packages)') and sourcemap-no-sources-content exit 0 ('72 built package(s) swept - 598 map(s), none embed source text'). check:pm-skill-ratchet's last line: 'declared cross-file moves: 2, total ceilings down 486 lines'. --ran (dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE, each family at its final reading): exit 0, 'Run reconciliation — 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN', with rerun-failed --self-test recorded outside the derivation. node scripts/pm/check-governed-merges.mjs --pr 25186: exit 3 ('the given paths ARE governed'), 'GOVERNED — Tier S', 7 paths read from the PR's file list, 1 on the register (.claude/** x1, references/platform-readings.md), +1353/-55. git fetch origin main, then git merge-tree --write-tree origin/main HEAD against origin/main 2069b81: exit 0, a tree id alone (747bc3ac1a), no conflict. Main's five commits since the merge base touch none of the 7 paths, so no merge was made. CI check-runs on 49804f2, read once and not waited on: 41, all completed, 33 success and 8 skipped, none red. Required: Lint & Repo Gates=success; TypeScript Type Check=success; Test Core=success; Build Core=success (it ran this time); Governed Surface Queue Guard=success. Also Test Core 1/6 to 6/6, Type Check workspace, source gates, consumer gates and debt ledger, every Lint lane including PM dispatch-gates and scoped tooling self-tests, Validate Package Dependencies, Check PR Size, Check Documentation Links, Dogfood Regression Gate, Auto Label, filter and the four claim guards=success. Check Changeset=skipped: the synchronize payload already carries skip-changeset, the job's documented fast path. The rest were skipped: opt-in harnesses, Console Pin Gate, Temporal Conformance, Dogfood shards, Build Docs, Dogfood Verify CLI.",
"mcp_calls": "0 this round. All reads went through gh api GET: the addendum comment, the PR's check-runs at 49804f2, and this comment's read-back. Last round: 5, all read tools (mcp__github__pull_request_read x3, mcp__github__issue_read x2). No write tool in either round.",
"api_writes": "1 — this os-dev-report comment, via scripts/pm/post-stamped.mjs through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches; the relay run sends POST /repos//issues/25169/comments). git push, which is not a REST write: one push, f2ee5ad..49804f2, fast-forward. No PR body PATCH; the seat writes the body from pr_body_replacements.",
"pr_body_replacements": [
{
"body_line_at_f2ee5ad67d": 4,
"replace": "The fleet-write relay gains one fenced op,
run_rerun_failed. It re-runs only the failed jobs of oneci.ymlpull-request run, once, asobjectstack-fleet[bot], through a relay-only seat wrapper,scripts/pm/rerun-failed.mjs..github/workflows/fleet-write.ymlis not edited: the existing stroke-scopedpermission-actionsinput carries the new op (ruling 3, evidence below). The card stays open, because its last proof is the first real use on a genuine flake after this merges. No real use was made here, since the relay runs main's code.","with": "The fleet-write relay gains one fenced op,
run_rerun_failed. It re-runs only the failed jobs of oneci.ymlpull-request run, once, and only while that run's head is still its pull's current head, asobjectstack-fleet[bot], through a relay-only seat wrapper,scripts/pm/rerun-failed.mjs..github/workflows/fleet-write.ymlis not edited: the existing stroke-scopedpermission-actionsinput carries the new op (ruling 3, evidence below). The card stays open, because its last proof is the first real use on a genuine flake after this merges. No real use was made here, since the relay runs main's code."},
{
"body_line_at_f2ee5ad67d": 6,
"replace": "## The row and its six fences, with where each is enforced",
"with": "## The row and its seven fences, with where each is enforced"
},
{
"body_line_at_f2ee5ad67d": 12,
"replace": "| workflow | the run's
pathis inRERUN_FAILED_WORKFLOWS, a closed list matched whole. Today it is exactly.github/workflows/ci.yml(measured spelling on run 39713789453: no ref suffix). It is a constant inops.mjs, never a payload key. | relay executor, on the run it reads immediately before the POST | wrapper, before the dispatch |","with": "| workflow | the run's
pathis inRERUN_FAILED_WORKFLOWS, a closed list matched whole. Today it is exactly.github/workflows/ci.yml(measured spelling on run 39713789453: no ref suffix). It is a constant inops.mjs, never a payload key. | relay executor, on the run it reads just before the POST | wrapper, before the dispatch |"},
{
"body_line_at_f2ee5ad67d": 15,
"replace": "| failed |
statusiscompletedandconclusionisfailure| relay executor, same read | wrapper |","with": "| failed |
statusiscompletedandconclusionisfailure| relay executor, same read | wrapper |\n| head | the run'shead_shais the CURRENT head of its pull. The pull is the run'spull_requests[0].number, and its head is read with that pull's ownGET /repos/{repo}/pulls/{n}, never from the copy the run embeds. A run with nopull_requestsentry (a fork pull request's run, or the field empty) is refused by name.ci.yml's concurrency group is keyed by the pull number withcancel-in-progress: true, so a stale head's re-run would cancel the current head's run. | relay executor, on the pull it reads right after the run, the last request before the POST | wrapper |"},
{
"body_line_at_f2ee5ad67d": 19,
"replace": "The four run fences share one set of predicates,
RERUN_FAILED_FENCES/rerunFailedFenceFailuresinops.mjs. The executor'srerunFailedPreflightreads attempt 1's job list first, as the read-back's baseline. It then readsGET /repos/{repo}/actions/runs/{run_id}, which is the last request before the POST, and judges the four fences on that answer. A run that changed between the request and the relay's run, for example one a person re-ran meanwhile, is refused there. A fence that fails, an unreadable run, an unreadable baseline or a baseline with no failed job stops the action with its POST rownot sent: zero writes, exit 5, every failing fence named with what the run read. The wrapper judges the same four fences earlier and names the one that fails. That check is advisory and never replaces the executor's.","with": "The five run fences share one set of predicates,
RERUN_FAILED_FENCES/rerunFailedFenceFailuresinops.mjs; each takes the run and the pull's own answer. The executor'srerunFailedPreflightreads attempt 1's job list first, as the read-back's baseline. It then readsGET /repos/{repo}/actions/runs/{run_id}and, for the pull the run names, that pull'sGET /repos/{repo}/pulls/{n}, the last two requests before the POST, and judges the five fences on those answers. A run that changed between the request and the relay's run, for example one a person re-ran meanwhile, or a pull pushed to meanwhile, is refused there. A fence that fails, an unreadable run or pull, an unreadable baseline or a baseline with no failed job stops the action with its POST rownot sent: zero writes, exit 5, every failing fence named with what was read. The wrapper judges the same five fences earlier and names the one that fails. That check is advisory and never replaces the executor's."},
{
"body_line_at_f2ee5ad67d": 60,
"replace": "The file's line budget is 120 bytes and its ceiling has zero headroom, so the line names the tool bare, as that file names every tool. It is paid for by deletion: it replaces the dated reading "读数 2026-09-13:席位 REST
/jobs/{id}/rerun403、MCPrerun_failed_jobs201 ⇒ 重跑可做,锁 1 未禁。". The same REST 403 / MCP 201 reading stands at the file's earlier line, "读数:POST /actions/runs/{id}/rerun-failed-jobsREST 回 403 而 MCP 回 201。".check:pm-skill-ratchetstays green at 460 of 460 lines..claude/agents/os-dev.mdis not touched: a dev never re-runs.","with": "The file's line budget is 120 bytes and its ceiling has zero headroom, so the line names the tool bare, as that file names every tool. It is paid for by deletion: it replaces the dated reading "读数 2026-09-13:席位 REST
/jobs/{id}/rerun403、MCPrerun_failed_jobs201 ⇒ 重跑可做,锁 1 未禁。". The same REST 403 / MCP 201 reading stands at the file's earlier line, "读数:POST /actions/runs/{id}/rerun-failed-jobsREST 回 403 而 MCP 回 201。".check:pm-skill-ratchetstays green at 460 of 460 lines..claude/agents/os-dev.mdis not touched: a dev never re-runs. The head rule does not fit as well: the line measures 119 of its 120 bytes, so theheadfence alone carries it, in code."},
{
"body_line_at_f2ee5ad67d": 66,
"replace": "- it reads the run and judges the four fences (advisory, exit 2 naming each failing fence, nothing sent);",
"with": "- it reads the run and then its pull by the pull's own GET, and judges the five fences (advisory, exit 2 naming each failing fence, nothing sent); a pull it cannot read is a prerequisite refusal (exit 3);"
},
{
"body_line_at_f2ee5ad67d": 70,
"replace": "- after a failed run it re-reads the run and says what the fences read now.",
"with": "- after a failed run it re-reads the run and its pull and says what the fences read now."
},
{
"body_line_at_f2ee5ad67d": 76,
"replace": "
node scripts/pm/check-governed-merges.mjs --branch claude/issue-25169-relay-rerun-failedreads GOVERNED, Tier S. The only governed path is.claude/skills/pm-dispatch/references/platform-readings.md, and no Tier H path is in the diff. The size is 1,285 changed lines, under the 3,000 human-merge threshold. As the dispatch directs, the maintainer approves and merges this PR. No seat readies, queues or arms it.","with": "
node scripts/pm/check-governed-merges.mjs --pr 25186reads GOVERNED, Tier S, at head49804f28cf. The only governed path is.claude/skills/pm-dispatch/references/platform-readings.md, and no Tier H path is in the diff: the rootpackage.jsonis not on the register. The size is 1,408 changed lines, under the 3,000 human-merge threshold. As the dispatch directs, the maintainer approves and merges this PR. No seat readies, queues or arms it."},
{
"body_lines_at_f2ee5ad67d": "80-88",
"replace": "All local readings below were taken at head
f2ee5ad67d, the final commit: a merge oforigin/main, which brought two commits that touch no file this PR touches.git merge-treeagainstorigin/mainwas clean before the merge.\n\n-node scripts/pm/dispatch-gates.mjs --commandsderived 51 families from the six changed paths. All 51 ran with exit 0, and--ranreconciles: "51 derived famil(ies) accounted for — 51 run, 0 NOT-MEASURED (a DERIVED zero — all 51 recorded an exit code and none of them is 3)".\n-pnpm check:pm-fleet-write-validate: 190 cases across 12 batteries. The new battery has 30 cases, and the floor rose from 11 to 12.\n-pnpm check:pm-fleet-write-execute: 184 cases across 19 batteries. The new battery has 17 cases, and the floor rose from 18 to 19.\n-pnpm check:pm-fleet-write-dispatch: 247 cases across 22 batteries. The new seat battery has 6 cases, the conformance adds the new sender and the relay-only roster, and the floor rose from 21 to 22.\n-node scripts/pm/rerun-failed.mjs --self-test: 28 cases across 5 batteries. It lies outside the derivation; see the notes.\n-pnpm check:pm-dispatch-gates(fast tier) and every other derived family: exit 0. That includescheck:pm-skill-ratchet,check:pm-skill-id-lint,check:pm-write-pace,check:entry-guard,check:self-test-wired,check:nul-bytesandcheck:doc-formula-expressions; the last ran after a build of its two declared prerequisite packages under the verify lock.\n- CI: the PR's check-runs are the record. Ablation: none, because the diff is scripts and a doc line.","with": "All local readings below were taken at head
49804f28cf, the final commit. Fix round 1 added two commits onf2ee5ad67d:120ca3ca58(theheadfence) and49804f28cf(the CI wiring).git merge-tree --write-tree origin/main HEADagainstorigin/main2069b81c26is clean (exit 0, a tree id alone), and none of main's five newer commits touches a file this PR touches, so no merge was made.\n\n-node scripts/pm/dispatch-gates.mjs --commandsderived 59 families from the seven changed paths; the rootpackage.jsonadds eight. All 59 ran. On the fresh worktree, five first answered exit 3, PREREQUISITE NOT MET:check:doc-formula-expressions,check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closureandcheck:sourcemap-no-sources-content. Each passed after the build it declares, run under the verify lock: its two packages, then the whole workspace (76 of 76 tasks).--ranreconciles: "59 derived famil(ies) accounted for — 59 run, 0 NOT-MEASURED (a DERIVED zero — all 59 recorded an exit code and none of them is 3)".\n-pnpm check:pm-fleet-write-validate: 193 cases across 12 batteries. The new battery has 33 cases (30 before theheadfence), and the floor rose from 11 to 12.\n-pnpm check:pm-fleet-write-execute: 188 cases across 19 batteries. The new battery has 21 cases (17 before theheadfence), and the floor rose from 18 to 19.\n-pnpm check:pm-fleet-write-dispatchnow runsdispatch.mjs --self-testand thenrerun-failed.mjs --self-test. The first: 247 cases across 22 batteries. The new seat battery has 6 cases, the conformance adds the new sender and the relay-only roster, and the floor rose from 21 to 22. The second: 31 cases across 5 batteries, its seat-fence battery 11 (8 before theheadfence).lint.ymlalready runs this script, so the wrapper's self-test reaches CI with no workflow edit.\n-pnpm check:pm-dispatch-gates(fast tier) and every other derived family: exit 0. That includescheck:pm-skill-ratchet,check:pm-skill-id-lint,check:pm-write-pace,check:entry-guard,check:self-test-wired,check:merge-driver,check:turbo-task-graphandcheck:nul-bytes.\n- CI: the PR's check-runs are the record. Ablation: none, because the diff is scripts, one npm script line and a doc line."},
{
"body_line_at_f2ee5ad67d": 93,
"replace": "-
scripts/pm/rerun-failed.mjs --self-testis run by no CI job. Wiring it needs a rootpackage.jsonalias and alint.ymlstep, both outside this card's file surface (lint.ymlis Tier H). CI still drives the wrapper'srelayMissProbethroughcheck:pm-fleet-write-dispatch. Carrier: none.","with": "- Fix round 1 wired
scripts/pm/rerun-failed.mjs --self-testinto the rootcheck:pm-fleet-write-dispatchscript, whichlint.ymlalready runs, so it reaches CI with no workflow edit."},
{
"body_line_at_f2ee5ad67d": 96,
"replace": "- The six ruled fences do not compare the run's head with the pull's current head. A re-run of a stale head's run cancels the current head's run, because both repositories' CI concurrency groups are keyed by PR number (platform-readings). The wrapper prints the run's head sha and pull number so the seat can compare.",
"with": "- Fix round 1 added the seventh fence,
head, by the seat's ruling on this PR. A stale head's re-run would cancel the current head's run, becauseci.yml's concurrency group is keyed by the pull number withcancel-in-progress: true. pm-dispatch SKILL.md's MCP exception already requires checking the head first, so the relay path is no weaker than the old one. The fence does not require the pull to be open; that was not ruled. Carrier: none."},
{
"body_line_at_f2ee5ad67d": 100,
"replace": "- 改了什么:中继新增一个只做「重跑失败任务」的操作。它只认
ci.yml的 PR 运行,只在首次运行失败后重跑一次,并且只重跑失败的 job。席位通过新脚本scripts/pm/rerun-failed.mjs调用,身份是objectstack-fleet[bot]。pm-dispatch 参考文档补了一行使用规则:先写明为何判为 flake,再重跑,二次失败即真红。","with": "- 改了什么:中继新增一个只做「重跑失败任务」的操作。它只认
ci.yml的 PR 运行,且只认 head 仍是该 PR 当前 head 的那次运行;只在首次运行失败后重跑一次,并且只重跑失败的 job。席位通过新脚本scripts/pm/rerun-failed.mjs调用,身份是objectstack-fleet[bot]。pm-dispatch 参考文档补了一行使用规则:先写明为何判为 flake,再重跑,二次失败即真红。根package.json的check:pm-fleet-write-dispatch同时跑该脚本的自测,CI 由此覆盖它,工作流不改。"},
{
"body_line_at_f2ee5ad67d": 102,
"replace": "- 风险与代价(含回滚):这一笔的令牌多带
actions: write,所以加了六道闸。工作流、事件、仅一次、已失败这四道由中继在执行时对 run 本身实判;独占一笔与令牌只为这一笔铸造这两道由校验器判。其余actions/*路径(整跑重跑、单 job 重跑、取消、删除、缓存、制品、密钥、变量)仍全部拒绝,有自测钉住。工作流文件未改。回滚只需还原本 PR,无数据迁移。","with": "- 风险与代价(含回滚):这一笔的令牌多带
actions: write,所以加了七道闸。工作流、事件、仅一次、已失败、head 为该 PR 当前 head 这五道由中继在执行时实判(head 用 PR 自己的 GET 读,不信 run 里嵌的副本);独占一笔与令牌只为这一笔铸造这两道由校验器判。其余actions/*路径(整跑重跑、单 job 重跑、取消、删除、缓存、制品、密钥、变量)仍全部拒绝,有自测钉住。工作流文件未改。回滚只需还原本 PR,无数据迁移。"}
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — .github/workflows/fleet-write.yml's comments (header, validate step, permission-actions input) still call workflow_dispatch the ONE consumer of actions write and say the op table has no re-run row. Ruling 3 kept the workflow out of this PR, so that prose is stale; fixing it is a Tier H comment-only edit · dedupe: fleet-write.yml actions write comment, permission-actions consumer, workflow comment drift",
"carrier: maintainer (the seat's ruling) · noted, not filed — .claude/settings.json has no allow rule for node scripts/pm/rerun-failed.mjs or its --use-env-proxy form, while every sibling write tool has both, so a seat's first use may meet a permission prompt (not measured) · dedupe: settings allow rerun-failed, permissions allow pm tool",
"carrier: none · noted, not filed — RERUN_FAILED_WORKFLOWS is a list of paths, as the card spells it, so it admits .github/workflows/ci.yml in any fleet repository the envelope names (objectui has one). The sender gate still requires write on that repository · dedupe: rerun allowlist per repo, ci.yml objectui rerun",
"carrier: none · noted, not filed — the head fence does not require the pull to be open; that was not ruled. A closed pull whose head still equals the run's head_sha passes it · dedupe: rerun closed pull, head fence pull state",
"carrier: none · noted, not filed — the usage line replaced the dated 2026-09-13 reading (seat REST jobs/{id}/rerun 403, MCP rerun_failed_jobs 201, lock 1 not forbidding it). Its MCP half survives at platform-readings.md:120 and SKILL.md:92, but the seat REST jobs/{id}/rerun 403 reading now survives nowhere in the file. A reading lost to the ratchet's zero headroom, not a defect · dedupe: jobs rerun 403 reading, platform-readings rerun cluster"
]
}
objectstack-fleet commentedon Oct 22, 2026
Landed ·
domain:devxseat 1 ·session_01FM29ZqLTrnPNXxijioZwMp· 2026-10-22T09:58Z2919ddb875. It is governed, Tier S, approved by the maintainer (os-zhuang, 2026-10-22T09:33Z, on head49804f28cf). Seat review6336734937;Refs #25169origin/main, by content49804f28cf:package.jsone28703fbf8,ops.mjs6b3e31bc88,validate.mjs9293ea97f5,dispatch.mjs815c5bf895,execute.mjse5dc1de5a7,rerun-failed.mjsfa5511bfb7,platform-readings.md93106aea40On
main, the relay carriesrun_rerun_failed, and a seat calls it asnode scripts/pm/rerun-failed.mjs --run ID.ci.ymlpull-request run, once, asobjectstack-fleet[bot].actions: write.State change: none on this act. The card's last proof, one real use on a PR run read back, can only happen on a genuine flake after this merge. The next comment releases the card to
pm:on-holdwith thatRestart-when:.For the maintainer, open from the seat review:
:92's MCPrerun_failed_jobsexception retire?.claude/settings.jsonhas no allow rule fornode scripts/pm/rerun-failed.mjs.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Hold, and release of claim
6334469056·domain:devxseat 1 ·os-warren·session_01FM29ZqLTrnPNXxijioZwMp· 2026-10-22T09:59ZRelease: session
session_01FM29ZqLTrnPNXxijioZwMp· reason: the seat clocks out on the maintainer's 「当前任务处理完,合并后就下班」, with this card's code landed (PR #25186,2919ddb875) · to:pm:on-hold, for the nextdomain:devxseatThe card's done-when has one part left: "One real use on a PR run, read back". The relay runs
main's code, so the op could not run before the merge, and a real use needs a genuine flake. This seat sets the hold. Its date is today, its reason is the one above, and its authority is the seat's grading.Restart-when: a
ci.ymlpull_requestrun on objectstack-ai/objectstack fails at attempt 1 in a way a seat has written down as a flake. That seat runsnode scripts/pm/rerun-failed.mjs --run <id>once, posts the attempt-2 read-back here, and closes this cardcompleted.The assignee is cleared and
pm:dispatchedcomes off in the same act. The next seat re-claims the card when it restarts.Generated by Claude Code