Repository navigation
docs(pm-dispatch): pin the shared checkout to a detached origin/main before a subagent batch - #24685
Conversation
…before a subagent batch The pre-dispatch step fast-forwarded the session branch, so the cloud stop hook counted main's commits against a never-pushed origin ref of that branch and asked the seat to push a zero-diff mirror of main. Fetch, then check out origin/main detached: the hook reads an empty current branch and skips its branch block. State the real refusal surface of the new form (only dirty changes that conflict with the switch; divergence is not refused). Line-neutral: 241/241. Claude-Session: https://claude.ai/code/session_01HEdP5tPhqQfzwRmKDxwn85 Co-authored-by: Claude <noreply@anthropic.com>
Contract review — 2026-10-19T23:41ZServed-tier: Inputs read: card #24585 body and all three comments (triage 6270321855, claim 6279366886, os-dev-report 6279634704); PR #24685 body, file list (1 file, +4/-4) and the net diff against main; 38 check-runs on the head, all completed: 26 success, 12 skipped, 0 failure (Governed Surface Queue Guard, Lint · PM dispatch-gates, Check Changeset, and the three claim/closing guards are among the successes). Cited files read at origin/main (92ce3af at review time); every cited file and the runbook are byte-identical between the PR's merge base eadea57 and that main, so the premise reads the same on today's main. Nothing was built, run or re-run; the stop hook was read, never executed. ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读(终稿)· PR #24685 ·
|
Fixes #24585
Clause-②: no
What changed
One section of
.claude/skills/pm-dispatch/references/dispatch-runbook.md, the pre-dispatch step for a subagent batch (lines 137-141). The PM seat no longer fast-forwards its session branch on the shared checkout. It fetchesmainand checks outorigin/maindetached:(
REPO-ROOTstands for the file's existing placeholder spelling, which is unchanged in the file.)pull --ff-only origin main.--ff-onlymakes "a dirty or diverged checkout fail loudly". The measurements below show that half of that promise was never true for dirty changes, and that the new form drops the divergence half.Ruling of record: the maintainer's 「技能侧可选改法,立skills卡片」, recorded verbatim in the card body. The carded option is implemented exactly as written. The two items in the card's "Not in this card" section are untouched: the hook's own counting, and the stale local ref.
Line budget
check:pm-skill-ratchet: "is 241 lines (ceiling 241; headroom 0)").MAX_LINE_BYTES = 120: L137 49 to 57; L139 100 to 113; L140 87 to 111; L141 117 to 120.The three measurements the card asks for
All readings were taken on
origin/maineadea5720(this branch's base). The git-behaviour probe ran in a throwaway repository under the session scratchpad: a bare origin, an author clone, and a "shared" clone on a session branch whoseorigin/SESSION-BRANCHref was written locally and never pushed. The probe never touched the shared checkout, and it was deleted before this PR. The stop hook script itself was read only: it was not executed and not modified. The probe ran the same git commands the hook runs.1. Every reader of the shared checkout's branch name
git grep -nE "abbrev-ref|show-current|symbolic-ref|symbolic-full-name|@\{u\}|..." HEAD -- .claude/ scripts/pm/ .githooks/returns 11 hits. Three of them read a branch name. The others are prose about detached processes or clones..claude/hooks/guard-main-checkout.sh:120rev-parse --abbrev-ref HEAD, printed in the refusal text at L132 only. Whether to block is decided structurally (git-dir vs common-dir, L114-117).HEAD(probe E). The block decision is unchanged..claude/hooks/guard-main-checkout-bash.shscripts/pm/os-regen-merge.sh:891(mode_run)mainandHEAD.:729writes the name into its record only aftermode_runpassed, so only in a dev worktree.scripts/pm/check-harness-current.mjsrev-parse HEADas a sha (L124, L139, L141, L157) and no name..githooks/pre-commit,.githooks/pre-push2. Other fast-forwards in the skill or
os-dev.mdthat assume a branchgit grep -nE "git pull|pull --|快进|fast-forward|--ff\b|ff-only|共享检出|shared checkout|本地检出|primary checkout" HEAD -- .claude/skills/pm-dispatch/ .claude/agents/os-dev.mdreturns 11 hits. The only fast-forward is the one this PR replaces (old L140-141). The rest are:SKILL.md:31andos-dev.md:28say never edit the shared checkout. Compatible.reading-discipline.md:8says do not verifymainfrom the shared checkout's tree. Compatible.seat-lifecycle.md:21is the harness-current reading, which works by sha. Compatible.platform-readings.md:59uses 快进 for squash committer dates. Unrelated.Outside the skill,
scripts/check-skill-frame-freshness.mjs:136prints the remedygit fetch origin main && git merge --ff-only origin/main. It runs in whatever checkout invokes it;os-dev.md:151lists it as a dev-worktree local family. Probe D: on a detached HEAD, that merge fast-forwards HEAD with exit 0, andbranch --show-currentstays empty, so it is compatible.3. The stop hook's control flow, and whether it stays silent across rounds
Source:
/root/.claude/stop-hook-git-check.sh, md5d36da5d989abb71904a379c7de566b95, read only.current_branch=$(git branch --show-current), thenif [[ -n "$current_branch" ]]. Everything else lives inside that block: the signing check (L77-114) and the$upstream..HEADcount (L116-124).git branch --show-currentprints an empty string. The whole branch block is skipped and the hook exits 0. The card's claim is confirmed.Probe readings, using the hook's own commands:
branch --show-currentrev-list origin/BRANCH..HEADHEAD ^origin/mainclaude/sessAfter A2 the session branch ref is still present and unmoved. NOT MEASURED: a live seat session's stop hook across several real dispatch rounds. That needs this text landed and a seat running it, and the hook belongs to the harness, which this run must not execute. The seat can confirm it on its first rounds after landing.
Live reading of the shared checkout at this PR's open (read only): it is on
claude/eloquent-curie-6slepwat3a931fca1. Its remote branch exists at the same sha, a zero-diff mirror ofmainpushed by the seat. This is the cost the card names.origin/mainis already 2 commits ahead, so the next fast-forward under the old text would show 2 "unpushed" commits.The refusal surface, old vs new (probe B and C)
pull --ff-only(old)checkout --detach origin/main(new)M b.txt)So the old line's "dirty fails loudly" held only for B2 under either form. Its "diverged fails loudly" is lost: C1 and C2 both exit 0. The new L141 says this ("不拒分叉") instead of carrying the old promise forward. Divergence on the shared checkout already breaks the never-edit rule (
SKILL.md:31, plus the two PreToolUse guards). In C1 the commit survives on its branch; in C2 it survives only in the reflog.Gates
All 17 derived families exit 0 at
ee7b8181e(node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, change set derived from git: 1 path, +4/-4). Each exit code was captured before any pipe:node scripts/check-closing-keyword-parity.mjs: 0node scripts/check-closing-keyword-parity.mjs --self-test: 0node scripts/check-comment-mask-corpus.mjs: 0node scripts/pm/check-harness-current.mjs --self-test: 0pnpm --filter @objectstack/lint run check:doc-formula-expressions: the first attempt exited 3 (PREREQUISITE NOT MET: formula and lint not built), so nothing was measured. Afterturbo run build --filter=@objectstack/formula --filter=@objectstack/lintunder the verify lock, it exits 0.pnpm check:agent-test-spelling: 0pnpm check:cross-package-test-inputs: 0pnpm check:doc-authoring: 0pnpm check:driver-memory-census: 0pnpm check:gitlink-declared: 0pnpm check:nul-bytes: 0pnpm check:pm-governed-merges: 0pnpm check:pm-skill-id-lint: 0 ("87 file(s) clean")pnpm check:pm-skill-ratchet: 0 ("dispatch-runbook.md is 241 lines (ceiling 241; headroom 0)"; the 120-byte line check prints only on failure)pnpm check:refd-timer-probe: 0pnpm check:skill-frame-sync: 0pnpm check:watch-hint-literal: 0Reconciliation with
--ran(each line annotated with its exit code): "17 derived, 17 run, 0 NOT-MEASURED, 0 UNRUN", and "a DERIVED zero — all 17 recorded an exit code and none of them is 3".No package is touched, so there is no dependency-closure build and no package test or typecheck.
.claude/**publishes nothing, so this PR carriesskip-changeset.Acceptance notes
scripts/pm/check-harness-current.mjssays "the primary checkout is not advanced in place (worktree-first)". The runbook has advanced it in place before every batch, both before this PR (fast-forward) and after it (detached checkout). This is comment drift and nothing reads it. Carrier: none.os-regen-merge.shwill now refuse on the shared checkout (HEAD), where before it would have run on the session branch. This is a side effect in the safe direction, noted only.origin/claude/eloquent-curie-6slepw, and the seat pushed the shared checkout there twice as a zero-diff mirror ofmain.维护者速读(草稿)
改了什么 —— PM 席位每批派发子代理前,不再在会话分支上快进本地检出,改为取回 main 后以游离 HEAD 钉在
origin/main。只改技能手册的一节,4 行原位替换,行数不变。为什么改 —— 云端停止钩子按会话分支计未推送提交。快进让 main 的提交挂到会话分支上,钩子每轮提示推送。照做会推出与 main 零差异的分支,本席位已推过两次。游离 HEAD 下钩子跳过分支检查,提示消失。子代理读到的仍是最新定义。
风险与代价(含回滚) —— 新写法不再拒绝分叉,共享检出上若有本地提交会被丢下或留在原分支。但共享检出本就禁止编辑,有两道钩子把守。与切换冲突的脏改动仍被拒。回滚即还原这 4 行。
席位意见 ——
你要做的 ——
Generated by Claude Code