Skip to content

Harden the worktree recipe with --no-track, and name .git/config in the shared-namespace inventory - #13403

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13052-worktree-halfstate-config
Aug 30, 2026
Merged

Harden the worktree recipe with --no-track, and name .git/config in the shared-namespace inventory#13403
os-zhuang merged 1 commit into
mainfrom
claude/issue-13052-worktree-halfstate-config

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Part of #13052

⚠️ Deliberately not a closing reference: this PR lands the half of the card's remedy #1 that fits under the line ceilings, and escalates the other half. The card stays open on the escalation below.

Governed surface — landing posture

AGENTS.md and CLAUDE.md are on the governed-surface list, so this is the manual lane: PASS + draft + review request to an authorised approver + status comment. No auto-merge is armed and the PR is not queued (arming it would hit the Governed Surface Queue Guard and burn a queue cycle — the shape recorded on #13034).

What landed

Two line-neutral edits, both in the card's remedy #1.

1. The prescribed worktree recipe now carries --no-track — root AGENTS.md Prime Directive #11 and root CLAUDE.md. This is what stops the half state at its source: with default tracking, git worktree add -b writes the new branch's upstream keys (branch.NAME.remote / branch.NAME.merge) into the one .git/config every linked worktree shares, and that write can fail after the branch is created. --no-track skips the write entirely.

2. AGENTS.md §9 names the file — the shared-namespace inventory's not the config becomes not `.git/config` (+3 bytes, no reflow), so the inventory names the actual shared file rather than a category.

The --no-track claim, measured against git itself

Reproduced in an isolated scratch repo (bare origin + clone, nothing shared), on the same git this container runs, git 2.43.0:

recipe branch.NAME.remote after branch.NAME.merge after worktree HEAD
worktree add PATH -b BRANCH origin/main (current) origin refs/heads/main = origin/main
worktree add --no-track PATH -b BRANCH origin/main ABSENT ABSENT = origin/main
worktree add --no-track -b BRANCH PATH origin/main ABSENT ABSENT = origin/main

So --no-track removes the config write in either argument order and still bases the worktree on origin/main — the replacement recipe is not weaker on the property the recipe exists for. This very PR's worktree was created with it.

Does it change a later git pull origin main? Measured: no. git pull origin main in the --no-track worktree exits 0 and uses the explicit refspec, which never consults branch.NAME.merge. Neither instruction file documents git pull or --set-upstream anywhere, so no documented dev flow depends on the removed keys — and git push -u origin BRANCH, which the flow already prescribes, sets the upstream one command later (observed on this branch's own first push).

Incidental, and an argument in the same direction: under the current recipe the new branch's upstream is origin/main, so a bare git pull in a task worktree merges main into the feature branch silently. Under --no-track a bare git pull stops with a loud "no tracking information" error instead.

What did NOT land, and the arithmetic

The card's remedy #1 also asks that the shared-namespace inventory gain the .git/config layer — i.e. the treatment refs/stash and refs/remotes/* get in §9: named, with its mechanism and its consequence. That does not fit, and the shortfall is measured rather than estimated.

Both files sit at their ratchet ceiling with zero headroomAGENTS.md 1162/1162, CLAUDE.md 86/86 — so any prose addition must be paid for line-for-line inside the block being edited.

  • Minimum honest text for the layer (name the shared file · name the -b tracking write that hits it · state that the recipe therefore says --no-track): ≈ 190 bytes+2 lines at §9's ~90-byte wrap.
  • Lossless wording compression available inside that same block, itemised: ≈ 49 bytes (~19 from under the name of a "revert", and stagedas a "revert", staged; ~11 from dropping mechanical in the no-hook sentence; ~9 from restore against that commitrestore against it; ~10 of slack on the block's last line).
  • Shortfall: ≈ 140 bytes ≈ 1.5 lines. Closing it needs either re-wrapping untouched prose to a wider column (rewrap-as-funding — banned) or deleting unrelated §9 content to pay for it (also banned). The same arithmetic holds in CLAUDE.md: ≈ 75 bytes needed for the excerpt's config clause against ≈ 25 recoverable.

The ceiling is load-bearing here, not a formality — ablated and restored:

--- mutation confirmed on disk: marker occurrences = 1, lines now = 1163
✗ check-skill-line-ratchet: AGENTS.md is 1163 lines; the ratchet ceiling is 1162. …
   Raising a ceiling requires a maintainer ruling quoted in the PR.
--- restore leg: hash now [e48ff67…] vs HEAD [e48ff67…]   (git diff HEAD clean, marker count 0)

⇒ the prose half needs a maintainer ceiling ruling, quoted in the raising PR — the route this repo's own ratchet history already uses (the 958→961, 1149→1150, 1150→1158 and 1158→1162 raises). It is escalated on the card rather than smuggled in here.

Worth noting that the card itself pre-authorises this split: its remedy list makes the half-state paragraph conditional — "Recipe hardening: if the recipe stays as it is, add one line about the half state and its cleanup". The recipe did not stay as it is, so the narrative is not the operative guidance; naming the layer still is, and that is the part escalated.

Measured vs inferred — preserved

The card's own split is kept intact and nothing here upgrades it:

  • Measured: the half state (branch created, directory absent, git worktree list clean, retry failing on branch-already-exists) — reported from direct observation on the card; and, in this PR, the --no-track config-write behaviour in the table above.
  • Inferred: that the failing config write lost a lock race against a sibling worktree writing config in the same instant. The card did not attempt to reproduce it and neither did this PR. Nothing in the landed text asserts the cause — .git/config being one shared file that -b writes stands on its own, independent of why the write failed.

Verification

Gate family re-derived from the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (change set read by the script itself, not from a hand-written diff) — it returned exactly the dispatched list, no additions. Union run at d0e2ad434, exit codes captured before any pipe:

gate exit its own verdict line
pnpm check:pm-skill-ratchet 0 ✓ AGENTS.md is 1162 lines (ceiling 1162; headroom 0). · ✓ CLAUDE.md is 86 lines (ceiling 86; headroom 0).
pnpm check:pm-governed-prose 0 ✓ 2 instruction surface(s) name all 5 registered governed surfaces … and claim no others.
pnpm check:pm-governed-merges 0 ✓ check-governed-merges --self-test: 204 assertions …
pnpm check:agent-test-spelling 0 ✓ 0 violations — 402 file(s) · 4883 bare -- token(s) …
pnpm check:docs-audit-scope 0 ✓ docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s).
pnpm check:pm-skill-id-lint 0 ✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:required-contexts 0 ✓ 6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned …
node scripts/check-required-contexts.mjs 0 same, patrol invocation
node scripts/check-nul-bytes.mjs 0 check-nul-bytes: OK (scanned 7426 text file(s) … no raw ASCII control bytes).

check:required-contexts first exited 1 with PREREQUISITE NOT MET — the dependency yaml is not installed in a fresh worktree. That is a not-measured result, not a red gate — it was re-run green after pnpm install, and both readings are recorded rather than the convenient one.

Also verified: no line exceeds the 120-byte budget (AGENTS.md 235 → 113 bytes, 236 → 99, 397 → 87); CLAUDE.md 32 is 132 bytes and is inside a fence, which classifyLine exempts by shape.

Changeset

None, and skip-changeset applied. This diff publishes nothing from any package — two repo-root instruction files. The Check Changeset gate has no path-based exemption (it counts added .changeset/*.md against the merge base), so the label is the mechanism, and it is the standing precedent for this exact surface: the last eight commits touching AGENTS.md carry no changeset, including #11934, which last edited this very recipe in both files.

Out of scope

The same recipe text lives in objectui (CLAUDE.md:16 and AGENTS.md:234, both verified locally) and probably cloud. Not touched here — filed as objectstack-ai/objectui#6880 after a dedup search returned only prior members of the same family, none of them open, so the drift is tracked in the repo it lands in rather than widening this PR.


Generated by Claude Code

…it/config in the shared-namespace inventory

The prescribed 'git worktree add -b <branch> origin/main' writes the new
branch's upstream config (branch.NAME.remote / branch.NAME.merge) into the
one .git/config every linked worktree shares, and that write can fail AFTER
the branch is created -- leaving a branch with no worktree, a state the
recipe never mentions. --no-track skips the config write entirely and still
bases the worktree on origin/main; nothing in the agent flow needs the
upstream, because 'git push -u origin <branch>' sets one a command later.

Line-neutral in both files (AGENTS.md 1162/1162, CLAUDE.md 86/86).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63de
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants