Surfaced by local h5 test run (#169 follow-up). Bro flagged it from inside the session.
What happened
Bro `cd`'d into a worktree to run V2 verification verbatim. From inside the worktree, every subsequent `git branch -m` or other Bash call got blocked by the `git-guards.sh` PreToolUse hook — because `tmb_db_path` resolved to the worktree's stale, empty trajectory.db (which lacks the `plugin_config` policy keys), and git-guards treats missing keys as deny-by-default.
Bro's quote:
"I cd'd into the worktree to run V2 verification verbatim — that left CC's bash pwd inside the worktree, where a stale `.claude/tmb/trajectory.db` (a tracked file copied at worktree creation) doesn't have the plugin_config keys. Every subsequent `git branch -m` call gets blocked by the git-guards PreToolUse hook"
Root cause
Two compounding problems:
1. The scratch project (and any user's fresh project) has no `.gitignore` for `.claude/`
`tests/dogfood/lib/flow-helpers.sh:l5_setup_scratch_project`:
```bash
git init -q -b main
echo "init" > README.md
git add . && git commit -qm init # ← no .gitignore
mkdir -p .claude/tmb # ← .claude is NOT ignored
```
`l5_setup_scenario_state` then runs `git add . && git commit -qm "scenario setup files"`, which commits the trajectory.db that was just created. `git worktree add` checks out the committed tree → worktree contains a stale copy of the DB.
This affects real users too, not just the test framework. When bro activates in a fresh user project, nothing ensures `.gitignore` excludes `.claude/`. Same DB-leak-into-worktree footgun applies.
2. `tmb_db_path` falls back to `$(pwd)/.claude/tmb/trajectory.db` without walking up to git root
`scripts/hooks/lib/query-task.sh:tmb_db_path`:
```bash
p="$(pwd)/.claude/tmb/trajectory.db"
[ -f "$p" ] && echo "$p"
```
When pwd is inside a worktree at `/.claude/worktrees//`, this finds the stale worktree-local DB instead of the project-root DB. Hooks that depend on policy keys (git-guards) then fail-deny because keys appear missing.
The fix
Two-part:
Test framework (immediate): `l5_setup_scratch_project` should write `.gitignore` containing `.claude/` BEFORE the initial commit.
Plugin (real-user impact): First-run onboarding should ensure the user's project `.gitignore` includes `.claude/`. Either:
- The activation-routine hook (or a SessionStart hook) appends `.claude/` to `.gitignore` if missing.
- Or `tmb_first-run-onboarding` skill writes it.
Hook robustness (defense-in-depth): `tmb_db_path` should walk up to the git root and use `/.claude//trajectory.db`, OR honor an env var that the plugin sets at session start. Worktree-local DB lookups should never silently win.
SWE doctrine: Bro's verification step (in `tmb_validate-swe-output`) should use `git -C ` rather than `cd ` to keep pwd stable. Once trapped inside the worktree, every PreToolUse hook misfires.
Repro
Run any code-touching prompt that spawns SWE in a fresh scratch project. Inspect the worktree:
```
ls .claude/worktrees//.claude/tmb/trajectory.db # exists, stale
```
Surfaced by local h5 test run (#169 follow-up). Bro flagged it from inside the session.
What happened
Bro `cd`'d into a worktree to run V2 verification verbatim. From inside the worktree, every subsequent `git branch -m` or other Bash call got blocked by the `git-guards.sh` PreToolUse hook — because `tmb_db_path` resolved to the worktree's stale, empty trajectory.db (which lacks the `plugin_config` policy keys), and git-guards treats missing keys as deny-by-default.
Bro's quote:
Root cause
Two compounding problems:
1. The scratch project (and any user's fresh project) has no `.gitignore` for `.claude/`
`tests/dogfood/lib/flow-helpers.sh:l5_setup_scratch_project`:
```bash
git init -q -b main
echo "init" > README.md
git add . && git commit -qm init # ← no .gitignore
mkdir -p .claude/tmb # ← .claude is NOT ignored
```
`l5_setup_scenario_state` then runs `git add . && git commit -qm "scenario setup files"`, which commits the trajectory.db that was just created. `git worktree add` checks out the committed tree → worktree contains a stale copy of the DB.
This affects real users too, not just the test framework. When bro activates in a fresh user project, nothing ensures `.gitignore` excludes `.claude/`. Same DB-leak-into-worktree footgun applies.
2. `tmb_db_path` falls back to `$(pwd)/.claude/tmb/trajectory.db` without walking up to git root
`scripts/hooks/lib/query-task.sh:tmb_db_path`:
```bash
p="$(pwd)/.claude/tmb/trajectory.db"
[ -f "$p" ] && echo "$p"
```
When pwd is inside a worktree at `/.claude/worktrees//`, this finds the stale worktree-local DB instead of the project-root DB. Hooks that depend on policy keys (git-guards) then fail-deny because keys appear missing.
The fix
Two-part:
Test framework (immediate): `l5_setup_scratch_project` should write `.gitignore` containing `.claude/` BEFORE the initial commit.
Plugin (real-user impact): First-run onboarding should ensure the user's project `.gitignore` includes `.claude/`. Either:
Hook robustness (defense-in-depth): `tmb_db_path` should walk up to the git root and use `/.claude//trajectory.db`, OR honor an env var that the plugin sets at session start. Worktree-local DB lookups should never silently win.
SWE doctrine: Bro's verification step (in `tmb_validate-swe-output`) should use `git -C ` rather than `cd ` to keep pwd stable. Once trapped inside the worktree, every PreToolUse hook misfires.
Repro
Run any code-touching prompt that spawns SWE in a fresh scratch project. Inspect the worktree:
```
ls .claude/worktrees//.claude/tmb/trajectory.db # exists, stale
```