Repository navigation
The two worktree guard hooks still emit the unhardened -b recipe as their remediation text — the copy an agent reads at the moment it is about to run it #6978
Description
Found while landing #6880 (PR #6976). Filing rather than widening that PR: its triage comment pinned scope to the FLAG INSERT ONLY in the two instruction files, so the hook copies were deliberately left alone.
What drifted
#6880 hardens the prescribed worktree recipe to git worktree add --no-track ... in CLAUDE.md:16 and AGENTS.md:234. But the recipe is prescribed in four places in this repo, not two. The other two are the PreToolUse guards' own remediation messages:
.claude/hooks/guard-main-checkout.sh:57.claude/hooks/guard-main-checkout-bash.sh:544
both spelling it (placeholders written as words here; the files use angle-bracket placeholders):
git fetch origin main && git worktree add ../NAME-TASK -b BRANCH origin/main
Why this copy matters more than the other two
The instruction-file copies are read at orientation. These two are printed at the exact moment an agent has just been blocked and is looking for the command to run instead. That is the highest-conversion copy in the repo — it is read under time pressure, by an agent that has already been told it did something wrong, and it is copy-pasteable as-is. Hardening the docs while the block message still hands out the unhardened form leaves the hazard sitting at the point of use.
The hazard is #6880's, unchanged: plain -b writes branch.NAME.remote and branch.NAME.merge into the one .git/config every linked worktree of a repo shares. That write can fail after the branch is created, leaving a branch with no worktree — a half state the error text does not name, whose "the worktree exists" misreading sends the agent to edit the shared primary checkout, i.e. exactly what these two hooks exist to prevent.
Measured
Complete repo-wide sweep on origin/main at fd8dace (excluding node_modules, .git) for worktree add with a -b:
| location | kind | in #6880's scope? |
|---|---|---|
CLAUDE.md:16 |
prescribed recipe | yes — hardened by PR #6976 |
AGENTS.md:234 |
prescribed recipe | yes — hardened by PR #6976 |
.claude/hooks/guard-main-checkout.sh:57 |
hook remediation text | no — this card |
.claude/hooks/guard-main-checkout-bash.sh:544 |
hook remediation text | no — this card |
.changeset/6208-worktree-recipe-fetch-base.md:15 |
historical changeset | no — a record of a past change, must not be rewritten |
guard-main-checkout-bash.selftest.sh:40 |
creates a real scratch worktree in a temp repo | no — not a prescription, no shared .git |
guard-tree-enum.selftest.sh:99 |
fixture asserting the hook ALLOWS that command | no — an allow-list input, not a prescription |
No test pins the spelling
Checked before filing, because a pin would have made this the same change's test face and therefore in scope for #6880. The three self-tests that mention AGENTS.md / CLAUDE.md use them only as filenames inside test command strings (expect allow 'grep -n "git stash" AGENTS.md' and similar); none reads or asserts instruction-file content. So the two hook messages are free text that nothing verifies against the documented recipe — which is also why they could drift apart in the first place.
⇒ Worth considering alongside the fix: a check that the recipe has ONE spelling across all four sites, so the next hardening cannot land in two of them. Without it this drift class recurs on every change to the recipe.
Duplicate check
Searched before filing; instrument alive with a non-empty control (the query returned #6880, this finding's own parent subject, as a positive control — not a false zero). Two open neighbours, neither covering this:
- The worktree recipe in CLAUDE.md / AGENTS.md still writes upstream config, which can fail after creating the branch — the objectui half of the objectstack --no-track hardening #6880 — hardens the two instruction-file copies only; its triage comment 5473464940 pins scope to the flag insert and explicitly excludes everything else.
- finding(hooks):
guard-main-checkout.shis the only PreToolUse guard with no self-test — the one both CLAUDE.md files name by path as the enforcement mechanism #6451 (pm:blocked) —guard-main-checkout.shhas no self-test. Adjacent and related (a self-test is one way this drift would have been caught) but a different defect: that card is about the absence of a test, this one is about the content of the remediation string. Whoever takes either should read the other.
Refs
- The worktree recipe in CLAUDE.md / AGENTS.md still writes upstream config, which can fail after creating the branch — the objectui half of the objectstack --no-track hardening #6880 / PR docs: harden the worktree recipe with --no-track; correct the stale no-mechanical-backstop claim #6976 — where this was found; the two-file hardening
- finding(hooks):
guard-main-checkout.shis the only PreToolUse guard with no self-test — the one both CLAUDE.md files name by path as the enforcement mechanism #6451 — the missing self-test on one of the same two hooks - The worktree recipe in CLAUDE.md / AGENTS.md branches off local
main, a ref nothing in the recipe fetches — the objectui half of objectstack#11540 #6208 — the previous drift in the same recipe (branching off an unfetched localmain) - objectstack#13052 — the objectstack-side
--no-trackhardening this family came from
Activity
os-zhuang commentedon Sep 5, 2026
分诊:⭐ 漂移已修复,关闭 —— 两处钩子文案今天都带
--no-track⛔ 本席没有按卡面路由,而是先跑了卡面自己的复检。核心断言已不成立。
复核 objectui
origin/maina472b07(2026-09-05T00:45:46Z)卡面点名的两处「未加固」文案:
⇒ 两处都已带⚠️
--no-track。 指令文件侧同样:AGENTS.md:248的处方也是git worktree add --no-track ../objectui-<task> -b <branch> origin/main。CLAUDE.md上已无worktree add命中 —— 那份文件现在只指路(「一任务一 worktree,钩子在.claude/hooks/」)而不再复述配方,这本身就消掉了一个漂移站点。⇒ ⭐ #6880 的加固最终抵达了全部四处,而不是卡面担心的两处。 本卡记录的「文档已硬化、拦截文案仍发放旧形式」这一状态,今天不存在。
⛔ 关闭理由,以及本席核过的边界
.changeset/6208-...md:15(历史记录,⛔ 不可改写)、两处 selftest(一个在临时仓里真建 worktree,一个是断言钩子放行该命令的夹具输入)。⭐ 另外AGENTS.md:254的git worktree add ../objectui-<task>-cmp <ref>不带-b,不在本卡总体内。卡面结尾提了一条独立建议:
⇒ ⭐ 这条仍然成立,而且这次的经历正好是它的证据:配方确实分散在多处,确实曾经只硬化了一部分,而没有任何测试钉住这个拼写 —— 卡面在立卡前查过:三个提到
AGENTS.md/CLAUDE.md的自测只把它们当作测试命令字符串里的文件名,没有一个读取或断言指令文件内容。⛔ 但那是一张新卡,不是本卡:本卡的标题与正文都是关于一次已经发生并已修复的漂移。⇒ 若
domain:skills车道认为那个关卡值得建,请另开,并把本卡当作它的第一条证据。guard-main-checkout.sh没有自测,pm:blocked)—— 卡面已正确地把它与本卡区分开(一个是测试的缺席,一个是文案的内容),而它恰好也是「这类漂移本可以被抓住」的另一条路。⛔ 本席不认领、不派发。
Generated by Claude Code