Uh oh!
There was an error while loading. Please reload this page.
fix(hooks): print the --no-track worktree recipe in both guards' remediation text - #7000
Merged
Merged
Conversation
…diation text `.claude/hooks/guard-main-checkout.sh` and `.claude/hooks/guard-main-checkout-bash.sh` each end their block message with the worktree recipe to run instead, and both created the branch with a plain `-b`. PR #6976 hardened the prescribed form in `CLAUDE.md` and `AGENTS.md`; the hooks' copy was left behind — and it is the higher-leverage copy, delivered at the exact moment an agent is about to act, so it is the one most likely to be run verbatim. The hazard is the one #6976 established: plain `-b` writes the new branch's upstream keys (`branch.NAME.remote`, `branch.NAME.merge`) into the one `.git/config` that 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. Read as "the worktree exists", the agent starts editing the shared primary checkout, which is the one thing these two guards exist to prevent. Additive, not a recipe rewrite: this repo's hooks already emitted the fetch-hardened base (`git fetch origin main && … origin/main`, landed for ui#6208), so the missing flag was the only surviving divergence from the prescribed form. One word added per file, net ±0 lines in both. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT
os-zhuang
approved these changes
Aug 31, 2026
os-zhuang
marked this pull request as ready for review
August 31, 2026 12:36
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#6977
What changed
Both worktree guards print a remediation recipe when they block — the copy an agent reads
at the exact moment it is about to act, and therefore the copy most likely to be run
verbatim. Both printed the recipe with a plain
-b. PR #6976 hardened the prescribed formin
CLAUDE.mdandAGENTS.mdwith--no-track; the hooks' copy was left behind.One word added per file, net ±0 lines in both:
.claude/hooks/guard-main-checkout.sh:57.claude/hooks/guard-main-checkout-bash.sh:544Both now emit exactly the prescribed form (verbatim from a live invocation of each hook,
with
$nameinterpolated):Why
The hazard is the one PR #6976 established: plain
-bwrites the new branch's upstreamkeys (
branch.NAME.remote,branch.NAME.merge) into the one.git/configthat everylinked 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. Read as
"the worktree exists", the agent starts editing the shared primary checkout, which is the
one thing these two guards exist to prevent.
Scope — additive, not a recipe rewrite
The premise was re-verified on
origin/mainbefore the first edit and it is narrower thanthe sibling card's. This repo's hooks already emitted the fetch-hardened base
(
git fetch origin main && … origin/main, landed for ui#6208), so the missing flag was theonly surviving divergence from the prescribed form. Measured on the freshly fetched tree:
grep -rc "no-track" .claude/hooks/returned 0 across all seven files there (four guards,three self-test matrices) before this change, and 1 in each of the two edited guards after.
Not in this PR
Each of these was examined and deliberately left alone:
.changeset/6208-worktree-recipe-fetch-base.mdquotes the pre-ui#6208 recipe as therecord of a past change. Editing a historical changeset rewrites that record —
adjudicated in PR docs: harden the worktree recipe with --no-track; correct the stale no-mechanical-backstop claim #6976, and restated on the card.
check-changeset-overwrite.mjsconfirms it mechanically: 0 modified, 0 deleted.
-cmpcomparison-tree lines inCLAUDE.md/AGENTS.mdcreate no branch andtake an explicit ref from the caller, so there are no upstream keys to write and the flag
does not apply. This is the same reading ui#6208 recorded for them.
.claude/hooks/guard-tree-enum.selftest.sh:99is a command payload fed to a differentguard, asserting that ordinary commands pass through it. It is test input, not text any
agent is told to run, and adding the flag there would change no coverage.
Verification
All at
efbfb15, the head of this branch. Nine checks, joined with&&so one verdictcovers all of them — exit 0.
Hook self-tests — the only gate that targets
.claude/hooks/**(
.github/workflows/hook-selftests.yml), all three matrices it runs:guard-main-checkout-bash.selftest.shguard-shared-stash.selftest.shguard-tree-enum.selftest.shIdentical to the pre-edit baseline, and that is the expected direction rather than a weak
result: no self-test case pins the remediation string, so none needed updating in this
change. Measured, not assumed — a
grep -rnfor four distinctive fragments of the two blockmessages (
fetch origin main,dedicated worktree,Blocked: editing on the shared,WRITES into the shared) across.claude/hooks/*.selftest.shandscripts/returns nomatch in any self-test.
A second measurement, reported because its absence is the answer rather than an assumption:
this repo ships no
guard-main-checkout.selftest.sh..claude/hooks/holds four guardsand three matrices;
guard-main-checkout.shis the one guard without one — so for that hookthe direct invocation below is the whole of its coverage.
Behavioural check — both hooks invoked directly with a payload targeting the shared
primary checkout. Each exits 2 (blocked, as designed) and prints the hardened recipe shown
above. The recipe it now prints was then fed back through all three of this repo's Bash
guards (
guard-main-checkout-bash,guard-shared-stash,guard-tree-enum): all three exit0, so the text is runnable and not self-blocked.
Repo gates that reach this diff —
check-control-bytes(5809 tracked text filesscanned),
check-changeset-presence,check-changeset-no-major,check-changeset-overwrite,check-shell-escape-residue,check-governed-queue-guard --self-test(132 cases). All ✅.bash -nclean on both edited files.Measurement — nothing lints shell files' contents in this repo. Verified on this tree
rather than taken from the workflow header that claims it:
eslint.config.jsscopes everyblock to
**/*.{ts,tsx}, and there is noshellcheckstep anywhere (the three matches inthe repo are two prose mentions and one
# shellcheck source=directive insidee2e/live/ci/start-backend.sh).check-shell-escape-residuereads fenced blocks inAGENTS.md,CLAUDE.md,skills/andcontent/docs/— it does not reach.claude/hooks/.So the self-test matrices plus the direct invocation above are the whole of the coverage
available for this diff, by measurement.
Changeset
check-changeset-presencereports no changeset is owed here (3 files changed, 0 of thempublished source of a released package). One is included anyway, with empty frontmatter,
because that is this repo's verified convention for a diff of this shape — the same form as
.changeset/6880-worktree-recipe-no-track.md(PR #6976),6208-worktree-recipe-fetch-base.mdand
6089-hook-selftests-step-names-drop-counts.md. The empty-frontmatter exemption is afirst-class pass in
scripts/check-changeset-presence.mjs.No
skip-changesetlabel is applied: in this repo that label is a phantom.scripts/__tests__/ci-cd-pipeline-doc.test.tspins that nothing under.github/orscripts/may mention it, so applying it would exempt nothing.Governed surface — draft, human merge
.claude/**is governed.check-governed-queue-guard --teston this diff returns⛔ GOVERNED — 2 of 3 path(s), and one governed path governs the whole pull request. This PRstays a draft: not flipped ready, not enqueued, no auto-merge armed. The merge is the
maintainer's, and a human merge is the review record.
Refs: PR #6976 · ui#6880 · ui#6208 · objectstack#13052. The objectstack twin
(objectstack#13663) covers that repo's hooks and is not addressed here.
Generated by Claude Code
Generated by Claude Code