Skip to content

fix(hooks): new-feature.sh bypasses the checkout guard — the script the block message recommends mutates the main checkout #910

Description

@admarble

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

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai-in-progressCurrently being worked by automationbugSomething isn't workingplannedImplementation plan approvedready-for-reviewReady for code review

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions