Repository navigation
pm-dispatch: the pre-dispatch fast-forward keeps the shared checkout on the session branch, so the cloud stop hook reports main's commits as "unpushed" and asks the seat to push #24585
Copy link
Copy link
Closed
Labels
area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:skillspriority:p2Medium: important, M3Medium: important, M3tooling
Description
Filing gate ③, a maintainer-directed task. The maintainer's word in domain:cli seat 1's session chat, verbatim: 「技能侧可选改法,立skills卡片」. It answers this seat's proposal in the same chat: let the seat keep the shared checkout detached at origin/main instead of on the session branch, so the stop hook skips it.
Filed by domain:cli seat 1 (seat post #6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T. ⛔ Not a claim. Lane, grade and area:* are triage's. Reader who acts: the domain:skills seat, which owns .claude/skills/pm-dispatch/** (governed, Tier S).
What happens (measured in this session, 2026-10-18 to 2026-10-19)
- The cloud environment's stop hook (
~/.claude/stop-hook-git-check.sh, not in this repository) countsgit rev-list "origin/$current_branch..HEAD" --countwheneverorigin/<session branch>resolves (its lines 39–40 and its final block). - The environment creates that ref locally at session start. Here
origin/claude/keen-feynman-9b3kh4was written 2026-10-18 06:33 ata422f04fe, andgit ls-remote origin refs/heads/claude/keen-feynman-9b3kh4answers nothing: the branch was never pushed. references/dispatch-runbook.md:140has the seat rungit -C <repo-root> pull --ff-only origin mainbefore each subagent batch. The shared checkout sits on the session branch, so every fast-forward moves HEAD alongmainwhile the stale ref stays put.- The result: the hook reported 98, then 103, 122 and 136 "unpushed commit(s)" at successive stops and asked for a push. At every reading,
git rev-list --count HEAD ^origin/mainwas 0: every commit was already onmain. - The cost: a push prompt after almost every turn, which the seat declines in chat each time. A seat that obeyed would publish a remote branch identical to
main, and the environment then asks for a draft PR with a zero diff.
The option the maintainer asked to be carded
Replace the fast-forward of the session branch with a detached checkout of main:
git -C <repo-root> fetch origin main && git -C <repo-root> checkout --detach origin/main--detachstill refuses to overwrite local changes, so a dirty checkout still fails loudly, which is the property:141asks of--ff-only.- The hook reads
git branch --show-current. It is empty on a detached HEAD, so the hook skips its branch block. - Dev worktrees are created from
origin/mainwithgit worktree add, so they are unaffected.
For the implementer to measure, not assumed here:
- Every reader of the shared checkout's branch name, for example
scripts/pm/check-harness-current.mjsand anything that runsgit rev-parse --abbrev-ref HEADthere.scripts/pm/os-regen-merge.sh(:729,:891) reads it only in a dev worktree. - Whether a fast-forward elsewhere in the skill or
os-dev.mdassumes a branch. - That a seat session's stop hook stays silent across several dispatch rounds.
Constraints
check:pm-skill-ratchet:SKILL.md's line count only falls. The change belongs inreferences/dispatch-runbook.md, where the rule lives.check:pm-skill-id-lint: no issue numbers in the skill text.- Tier S (
.claude/**): an at-tier review before ready.
Not in this card
- The hook's own counting.
HEAD --not --remoteswould answer 0 here, and the script already uses that form in its signing block. It belongs to the Claude Code cloud environment, not this repository, and is reported upstream by the maintainer if wanted. - Deleting the stale local ref by hand, which is a per-session workaround.
Dedupe
- MCP
search_issues"stop hook unpushed commits PM seat shared checkout pull ff-only main session branch": 22 hits, all seat posts or unrelated cards (fleet-write: no relay op for PUT /pulls/{n}/update-branch — the sanctioned base sync (rest-channel.md:49) runs as bare REST and authors the merge commit under the seat's personal account (PR #22002 head fab444b4) #22052, which is about the update-branch relay op). None names the stop hook. - "pm-dispatch shared checkout detached HEAD origin/main instead of session branch": 22 hits, with no card on the checkout's branch (pm-dispatch: a re-seated PM starts at the default concurrency (batch 3); a dispatch posture the maintainer gave a previous session (serial, batch N) ends with that session's shift and is not inherited through the handover brief #20154 concurrency, pm-dispatch: the allow-listed ccr ready flip was denied
[Auto-Mode Bypass]in a session whose checkout carried the allow rows from its first HEAD — and since #19997 a denied ccr call leaves a session seat no landing route #20048 the ccr flip, pm-dispatch: re-read the PR head before trusting anycheck_suite.completedevent — the event can name a stale SHA (#15803 section B2) #16372 stale SHAs). - The control query reaches the pm-dispatch family (pm-dispatch: a re-seated PM starts at the default concurrency (batch 3); a dispatch posture the maintainer gave a previous session (serial, batch N) ends with that session's shift and is not inherited through the handover brief #20154, pm-dispatch: the allow-listed ccr ready flip was denied
[Auto-Mode Bypass]in a session whose checkout carried the allow rows from its first HEAD — and since #19997 a denied ccr call leaves a session seat no landing route #20048). - Dedupe words: stop hook unpushed; shared checkout detached; dispatch-runbook ff-only session branch.
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:skillspriority:p2Medium: important, M3Medium: important, M3tooling
Activity
objectstack-fleet commentedon Oct 19, 2026
Triage: p2 ·
tooling·domain:skills·area:devpath·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T14:59ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
## Dedupe). The three answers are present.domain:skills:.claude/skills/pm-dispatch/references/dispatch-runbook.md:140, governed surface. Bothdomain:skillsseat posts are vacant, so the maintainer merges. It can ride the same skills PR as the filing-gate change filed this hour, if one author holds both.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 2 ·
domain:skillsseat 1 · 2026-10-19T23:09ZSession:
session_01HEdP5tPhqQfzwRmKDxwn85Account:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-24585-detached-main-checkoutWorktree:
objectstack-issue-24585Domain:
domain:skillsSeat:
domain:skills#1(seat post #7623)File surface:
.claude/skills/pm-dispatch/references/dispatch-runbook.md, the 〈subagent 批派发前置〉 section (lines 137–141 onorigin/main3a931fca1) only (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: TIER_DEFAULT(dispatch-gates --tier --repo objectstack-ai/objectstackat3a931fca1: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; areferences/**-only text card is built at the default tier; the compensating control is an isolatedCONTRACT_REVIEW_TIERreview before the Tier S landing)Clause-②: no
Responsibility:
n/a — not a defect card(filing gate ③, a maintainer-directed task)Thread-read: 6270321855
Serial constraints cleared: none — of the 17 open PRs, none touches
.claude/**orskills/**(file lists read at this claim; #24554 touchesscripts/pm/check-expected-skips.mjsandensure-pm-labels.sh, outside this surface); seat 2 (#19287) signed off 2026-10-19T16:53Z with no card in flight; PR #24602 (#24589) merged asc03b17d78and releasedfiling-gate.md,execution-duties.md,triage-duties.md; this seat's R1 PRs #24520 and #24521 merged 2026-10-19T14:45Z.domain:cliseat 1's session chat, recorded verbatim in this card's body by that seat; the carded option is the detached checkout oforigin/mainin place of the session-branch fast-forward.origin/main3a931fca1:dispatch-runbook.mdlines 140–141 read as the card quotes (88 and 118 bytes); the file is 241 lines against its 241-line ceiling, so the edit must be line-neutral;MAX_LINE_BYTESis 120..claude/andscripts/pm/at3a931fca1:.claude/hooks/guard-main-checkout.sh:120(rev-parse --abbrev-ref HEAD, used only to print the branch in its refusal text at line 132; a detached checkout printsHEAD);scripts/pm/os-regen-merge.shlines 729 and 891 (dev worktrees only);check-harness-current.mjscompares HEAD content withorigin/mainand reads no branch name. The dev re-measures these and the stop hook's behaviour on a detached HEAD.origin/claude/eloquent-curie-6slepwlocally; this seat has pushed the shared checkout to it twice today as a zero-diff mirror ofmain— the cost the card names.objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24585, "status": "done", "branch": "claude/issue-24585-detached-main-checkout", "pr": "https://github.com/objectstack-ai/objectstack/pull/24685", "session": "session_01HEdP5tPhqQfzwRmKDxwn85 (subagent run: the parent PM session's id)", "premise_still_valid": true, "summary": "dispatch-runbook.md, the subagent pre-dispatch section (L137-141), now says to fetch main and run checkout --detach origin/main. It no longer says to pull --ff-only on the session branch. L141 states the real properties: on a detached HEAD the stop hook skips its branch block; the session branch is never fast-forwarded; only dirty changes that conflict with the switch are refused; divergence is not refused. The rationale (a stale checkout gives the whole batch stale definitions) moved into L139. The change is 4 lines replaced in place: the file stays at 241 lines against a ceiling of 241, and every line is 120 bytes or less. The card's three measurements are in the PR body. (1) Branch-name readers: guard-main-checkout.sh:120 only prints the name (HEAD when detached) and blocks on a structural test. guard-main-checkout-bash.sh reads no branch name. os-regen-merge.sh:891 refuses main and HEAD, so it now refuses on the shared checkout, which is safer. check-harness-current.mjs reads a sha only. (2) The fast-forward this PR replaces is the only one in the skill or os-dev.md. scripts/check-skill-frame-freshness.mjs gives git merge --ff-only origin/main as its remedy, and that is compatible with a detached HEAD (probe D). (3) Stop hook, read only (md5 d36da5d989abb71904a379c7de566b95): L39-40 wraps the signing check and the upstream..HEAD count in a non-empty current_branch test, so a detached HEAD skips both. Confirmed. Its dirty and untracked checks (L27-37) still run first. The PR is draft, assigned to os-justin, labelled skip-changeset, and its body read back byte-identical (11830 bytes). The worktree is removed right after this comment posts (git worktree remove, no --force).", "tests": "No package is touched, so there is no package test or typecheck. A git-behaviour probe ran in a throwaway synthetic repo under scratchpad/issue-24585/probe: a bare origin, an author clone, and a shared clone on a session branch whose local origin ref was never pushed. It ran the stop hook's own git commands; the hook script was never executed. The probe dir was deleted before this report. Readings: A1, the old pull --ff-only after 3 main commits: branch=claude/sess, hook block entered, rev-list origin/claude/sess..HEAD = 3, HEAD ^origin/main = 0 (the reported symptom). A2, A3, A4, the new fetch plus checkout --detach over 3 rounds: branch --show-current empty, hook block skipped, HEAD equals origin/main, and the session branch ref is unmoved. B1, a dirty file not touched upstream: both forms exit 0 and carry the change. B2, a dirty file touched upstream: both exit 1 (would be overwritten). B3, an untracked collision: checkout --detach exits 1. C1, a local commit on the session branch: ff-only exits 128, detach exits 0 and the commit stays on the branch. C2, a local commit on the detached HEAD: detach exits 0 with 'leaving 1 commit behind' and the commit is reachable from 0 refs. D, merge --ff-only origin/main on a detached HEAD: exit 0, still detached. E, rev-parse --abbrev-ref HEAD when detached prints HEAD. No ablation: this is a docs-only change.", "gates": "17 derived (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack; change set from git: 1 path, +4/-4 vs merge base eadea5720), all run at HEAD ee7b8181e. Each exit code was captured before any pipe. Exit 0: check-closing-keyword-parity.mjs, the same with --self-test, check-comment-mask-corpus.mjs, pm/check-harness-current.mjs --self-test, check:agent-test-spelling, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:gitlink-declared, check:nul-bytes, check:pm-governed-merges, check:pm-skill-id-lint ('87 file(s) clean'), check:pm-skill-ratchet ('dispatch-runbook.md is 241 lines (ceiling 241; headroom 0)'; the 120-byte line check prints only on failure), check:refd-timer-probe, check:skill-frame-sync, check:watch-hint-literal. Exception: lint check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET: formula and lint not built), then exited 0 after turbo build --filter formula --filter lint under os-verify-lock (VERDICT command-exit 0, 108s). --ran reconciliation: '17 derived, 17 run, 0 NOT-MEASURED, 0 UNRUN', 'a DERIVED zero, all 17 recorded an exit code and none of them is 3'. PR check-runs, read once at open: 26 total, 2 success (Check Changeset; No other open PR may claim the same issue), 5 skipped, 19 in_progress, including Governed Surface Queue Guard, the Lint lanes and the Type Check lanes. CI convergence belongs to the seat. Local-only families: check:skill-frame-freshness is not owed (the four-axis frame file is untouched).", "mcp_calls": "0", "api_writes": "2 relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]. Stroke 1, pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft), then POST /repos/objectstack-ai/objectstack/issues/24685/assignees, then POST /repos/objectstack-ai/objectstack/issues/24685/labels. Stroke 2, comment: POST /repos/objectstack-ai/objectstack/issues/24585/comments (this report). Also 2 git pushes, which are not REST writes. Reads were GETs only: the issue, its comments, the PR and the check-runs.", "open_questions": [], "out_of_scope_findings": [ "carrier: none (Acceptance notes only, not filed). The header of scripts/pm/check-harness-current.mjs says 'the primary checkout is not advanced in place (worktree-first)', but the runbook advances it in place before every batch, both before and after this PR. This is comment drift and nothing reads it. Dedupe words: harness-current header primary checkout advanced in place", "carrier: none (noted, not a defect). os-regen-merge.sh mode_run refuses HEAD, so it now refuses on the detached shared checkout, where it would previously have run on the session branch. That is safer than before." ], "line_budget": "dispatch-runbook.md 241 lines before and after, ceiling 241, headroom 0. Bytes per changed line (no newline; MAX_LINE_BYTES 120): L137 49 to 57, L139 100 to 113, L140 87 to 111, L141 117 to 120. No re-wrap.", "files_changed": [".claude/skills/pm-dispatch/references/dispatch-runbook.md"], "deviations": [ "Base: the dispatch named 3a931fca1, but origin/main had moved to eadea5720 when the worktree was created. The two new commits (console injection assertion; service-automation runAs) touch neither .claude/ nor scripts/pm/. BASE is eadea5720.", "PM premise 1: the dispatch's 88 and 118 bytes for L140 and L141 include the newline. The ratchet counts without it: 87 and 117.", "L139 was rewritten too, inside the claimed section 137-141, so the 'stale checkout gives a stale batch' rationale survives within 120 bytes per line.", "The probe used a synthetic throwaway repo rather than a clone or worktree of this repo. Git semantics are the same, the shared checkout was never touched, and the probe was deleted before the report.", "Trailers: the commit carries the AGENTS.md model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-bearing trailer. The PR body ends with the AGENTS.md session-URL footer form, not the reminder's form.", "NOT MEASURED: a live seat session's stop hook across several real dispatch rounds. That needs the text landed and a seat running it, and the hook is the harness's, which this run must not execute. The probe's three rounds (A2-A4) and the hook's control-flow reading stand in for it." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
ACCEPT → PR #24685 comment 6279805520 ·
domain:skillsseat 1 ·os-justin·session_01HEdP5tPhqQfzwRmKDxwn85· 2026-10-19T23:43ZContract review PASS on head
ee7b8181e(record 6279791956,CONTRACT_REVIEW_TIER); CI 26 success / 12 roster skips / 0 failed; readied and armed by the seat's relay strokes (run 39343248254). Tier S: lands by the queue; the landing comment follows the merge.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Landing ·
domain:skillsseat 1 ·os-justin·session_01HEdP5tPhqQfzwRmKDxwn85· 2026-10-20T00:10Zc66324564c66324564is an ancestor oforigin/main(045dae8f2)gh-readonly-queue/main/pr-24685-…still presentclaude/issue-24585-detached-main-checkoutstill presentState change: the card closed
completedby the PR'sFixes;pm:dispatchedremoved. The new pre-dispatch step is in force for this seat from this fire on; the stop hook's behaviour on the detached shared checkout is being read at this seat's stops and will be noted here if it speaks.Generated by Claude Code