Found during adversarial validation of #906 (Design v2, "Adjacent defects"). A #901 gap. Verified against main at e2e57a7c.
Bug
The checkout guard in pre-tool.sh inspects only the top-level Bash command string the agent runs. ./scripts/new-feature.sh <issue> contains no guarded git verb, so the guard passes it through — and then the script runs, internally, in the main checkout:
# templates/scripts/new-feature.sh:148
git checkout "$BASE_BRANCH"
A session that does not hold the checkout lock can therefore switch the main checkout's branch out from under the holder, which is precisely what #901 exists to prevent. The guard's own refusal message even recommends ./scripts/new-feature.sh <your-issue> as the way to proceed — so the documented escape hatch is the bypass.
Severity
Moderate. It needs two concurrent sessions, but the path is not exotic: it is the one the block message tells the loser to take.
Fix options
- Guard inside the script. Have
new-feature.sh consult the checkout lock itself (sequant locks checkout check) and refuse when held by another session. Keeps enforcement next to the mutation, and covers direct terminal invocations the hook never sees. Needs the script to stay usable when the CLI is absent.
- Avoid the mutation.
git worktree add does not require the main checkout to be on the base branch; git worktree add -b <new> <path> <base-ref> reads the ref directly. If line 148 exists only to make the subsequent worktree add resolve, removing it closes the gap outright and is strictly better — no lock needed for an operation that no longer touches the main tree.
- Extend the hook to resolve known project scripts and inspect their contents — rejected: fragile, and it cannot see through arbitrary indirection.
Option 2 first if it holds; option 1 as the general backstop for other scripts.
Test plan
- Hold the checkout lock as session A; run
./scripts/new-feature.sh as session B; assert the main checkout's HEAD is unchanged.
- Assert the guard still allows the script when the lock is free or held by the caller.
- If option 2: assert worktree creation still works with the main checkout sitting on an unrelated branch with a dirty tree.
Found during adversarial validation of #906 (Design v2, "Adjacent defects"). A #901 gap. Verified against
mainate2e57a7c.Bug
The checkout guard in
pre-tool.shinspects only the top-level Bash command string the agent runs../scripts/new-feature.sh <issue>contains no guarded git verb, so the guard passes it through — and then the script runs, internally, in the main checkout:A session that does not hold the checkout lock can therefore switch the main checkout's branch out from under the holder, which is precisely what #901 exists to prevent. The guard's own refusal message even recommends
./scripts/new-feature.sh <your-issue>as the way to proceed — so the documented escape hatch is the bypass.Severity
Moderate. It needs two concurrent sessions, but the path is not exotic: it is the one the block message tells the loser to take.
Fix options
new-feature.shconsult the checkout lock itself (sequant locks checkout check) and refuse when held by another session. Keeps enforcement next to the mutation, and covers direct terminal invocations the hook never sees. Needs the script to stay usable when the CLI is absent.git worktree adddoes not require the main checkout to be on the base branch;git worktree add -b <new> <path> <base-ref>reads the ref directly. If line 148 exists only to make the subsequentworktree addresolve, removing it closes the gap outright and is strictly better — no lock needed for an operation that no longer touches the main tree.Option 2 first if it holds; option 1 as the general backstop for other scripts.
Test plan
./scripts/new-feature.shas session B; assert the main checkout's HEAD is unchanged.