fix(hooks): align both guard-main-checkout remediation recipes to the prescribed worktree form - #13727
Merged
Merged
Conversation
… prescribed worktree form
The remediation text is the highest-leverage copy of the worktree recipe: it
reaches an agent at the exact moment it is about to create a worktree. Both
hooks printed the unhardened form -- plain `-b` (which writes
branch.NAME.remote/merge into the .git/config every worktree of the repo
SHARES) off an unfetched local `main` (which bases the new tree on a stale
commit). Both halves were already adjudicated and already hardened in
AGENTS.md PD#11 and CLAUDE.md; only these two copies were left behind.
Printed recipe is now, in both hooks, with each file's own `${name}`
interpolation preserved verbatim:
git fetch origin main && git worktree add --no-track ../${name}-<task> -b <branch> origin/main
A comment above each heredoc records why both halves are load-bearing, so the
next reader does not "simplify" them back out.
Verified by rendering each hook end to end against the shared primary checkout
(exit 2, `${name}` expands, `\$HOME` still escaped) and by both hermetic
selftest matrices: guard-main-checkout 87 passed / 0 failed,
guard-main-checkout-bash 121 passed / 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT
huangyiirene
marked this pull request as ready for review
August 31, 2026 08:53
huangyiirene
enabled auto-merge
August 31, 2026 08:53
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 31, 2026
os-zhuang
approved these changes
Aug 31, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 31, 2026
os-zhuang
approved these changes
Aug 31, 2026
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 free
to 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 #13663
Both
guard-main-checkouthooks printed the unhardened worktree recipe as their remediation text — the copy an agent reads at the exact moment it is blocked, and therefore the highest-leverage copy of the recipe in the repo. It carried two hazards, both already adjudicated and both already hardened inAGENTS.mdPrime Directive 11 and inCLAUDE.md; only these two copies were left behind.--no-track— plain-bwritesbranch.NAME.remote/branch.NAME.mergeinto the.git/configthat every worktree of the repo SHARES (the 13052 / ui 6880 adjudications).main, with no fetch — an unfetched localmainbases the new tree on a stale commit (the ui 6208 adjudication).What changed
Two files, one printed line each, plus a comment above each heredoc recording why both halves are load-bearing so the next reader does not "simplify" them back out.
.claude/hooks/guard-main-checkout.sh(theEdit/Write/NotebookEditguard).claude/hooks/guard-main-checkout-bash.sh(the same write arriving throughBash)Each file's own
${name}interpolation is preserved verbatim — the printed recipe is now, in both:What was measured, not assumed
Premise re-derived on a freshly fetched tree (not taken from the card): the bad string sits at
guard-main-checkout.sh:57andguard-main-checkout-bash.sh:548, andgrep -c "no-track"over both files returns0/0. Both hold.No selftest case pinned the old string. The card anticipated one might. Grepping both selftest matrices — and then the whole tree — for every fragment of the remediation block (
-b <branch>,re-run your edits there,re-run the command there,dedicated worktree:) puts the only two occurrences in the hooks themselves. Nothing else asserts on this text, so nothing else needed updating. The two selftests that do cover these hooks areguard-main-checkout.selftest.shandguard-main-checkout-bash.selftest.sh(the stash guard's matrix is a different hook's); both were re-run:The bash guard's 121 matches the baseline
lint.ymlrecords for it onmain, so the matrix neither shrank nor grew. CI runs both anyway — theClaude hook guard self-testsstep discovers.claude/hooks/**/*.selftest.shat run time.The remediation actually renders. A string inside a heredoc is only correct if it survives interpolation, so each hook was driven end to end against the shared primary checkout and its stderr read:
Exit code 2 preserved in both,
${name}expands to the repo basename, and the bash guard's escaped\$HOMEin the "always fine" list is still escaped.Gates
Derived from the real diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(which reads its own change set from the merge base — no hand-built path list), then run at the final commit51007325. Exit codes captured with a redirect before the read, never through a pipe:pnpm check:agent-test-spellingpnpm check:bash32-floorpnpm check:doc-authoringpnpm check:pm-governed-mergespnpm check:skill-frame-syncpnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm check:nul-bytes(any edit)check:bash32-flooris the one that reads these files as shell: "24 tracked shell file(s) underscripts/**,.claude/hooks/**,.githooks/**name no bash 4+ construct…".check:doc-formula-expressionsfirst answeredPREREQUISITE NOT MET — @objectstack/formula is not built, then the same for@objectstack/lint; that is a not-measured reading, not a red gate, so both packages were built and the gate re-run to a real0.ESLint (
pnpm lint, which CI runs unconditionally) was not run repo-wide, and this is a declared narrowing with all three pieces of evidence:eslint.config.mjsrather than guessed:files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']..shis not in it.--format jsonover exactly the two changed paths: 2 results, 0 errors, 2 warnings, both"File ignored because no matching configuration was supplied."— i.e. this diff contributes zero files to ESLint's population.eslint.config.mjs, and a shell file cannot enter a type-aware program, so every untouched file's verdict is identical tomain's.No changeset
This diff is
.claude/**only and publishes nothing from any package, so it takes theskip-changesetroute. Verified against recentmainhistory rather than recalled — the last three.claude/**-only landings (5364d2e5,f4d53081, and the.claude/**half of3d089745) carry no.changeset/*.md, andpr-automation.yml's Check Changeset job re-reads labels live and exempts on an exact-matchskip-changeset. The label is applied to this PR.Governed surface
.claude/**is governed surface: this PR stays draft and is for human merge only. Not marked ready, no auto-merge, not enqueued.Out of scope, filed separately
The same unhardened recipe survives in four more agent-facing copies (
.claude/skills/checklist-author/SKILL.md,.claude/agents/os-dev.md,.claude/skills/dogfood-verification/SKILL.md, and the publishedskills/objectstack-pm-dispatch/SKILL.md). Those are a different file surface, one of them is a published skill with its own budget rules, and this card's claim declared the two hook files — so they are filed rather than ridden along here. The objectui twin (ui 6977) is being handled in that repo by another dev; nothing here touchesobjectui.Refs: 13663 · ui 6976 · 13052 / ui 6880 · ui 6208.
Generated by Claude Code
Generated by Claude Code