Skip to content

Commit 0d572b3

Browse files
lagameonclaude
andcommitted
fix(V3.2-P7): worktree-safe hook prefix — git-common-dir replaces show-toplevel
Hooks failed when Claude Code was opened from a git worktree because git rev-parse --show-toplevel returns the worktree path, not the main repo root, causing .memory/hooks/ to be unfindable. New prefix in generate_hooks_settings(): _r="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \ || exit 0; _r="${_r%/.git}"; cd "$_r" && ... - --git-common-dir always resolves to the main repo .git (not the worktree subdir) - --path-format=absolute gives a consistent absolute path regardless of CWD - bash suffix removal (no sed, no false match on .github paths) - Requires git 2.31+ (March 2021) Bug discovered in QTP worktree session; EFM had the same root cause. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a8794c6 commit 0d572b3

3 files changed

Lines changed: 40 additions & 3 deletions

File tree

.memory/lib/init.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,14 @@ def generate_hooks_settings() -> dict:
213213
# is editing files in a subdirectory like deployment/live_trading/).
214214
# The _root variable silently exits if not in a git repo, preventing
215215
# errors (and infinite Stop-hook loops) in non-git subdirectories.
216-
_root = '_r="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0; cd "$_r" && '
216+
#
217+
# Worktree-safe: --path-format=absolute --git-common-dir always returns
218+
# the main repo's .git path (not the worktree path), so hooks can find
219+
# .memory/hooks/ even when Claude is opened from a git worktree.
220+
# bash ${_r%/.git} strips exactly "/.git" at the end (no sed, no glob
221+
# issues with paths that contain ".github" directories).
222+
# Requires git 2.31+ (released 2021-03).
223+
_root = '_r="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || exit 0; _r="${_r%/.git}"; cd "$_r" && '
217224
return {
218225
"SessionStart": [
219226
{

.memory/tests/test_init.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,10 +428,12 @@ def test_hooks_commands_have_cd_prefix(self):
428428
for hook in group["hooks"]:
429429
cmd = hook["command"]
430430
if ".memory/hooks/" in cmd:
431+
# Worktree-safe: must use --git-common-dir (not
432+
# --show-toplevel which returns the worktree path)
431433
self.assertIn(
432-
'git rev-parse --show-toplevel',
434+
'git rev-parse --path-format=absolute --git-common-dir',
433435
cmd,
434-
f"{event_name} hook missing cd-to-root prefix",
436+
f"{event_name} hook missing worktree-safe cd-to-root prefix",
435437
)
436438
# Must suppress stderr and exit gracefully for non-git dirs
437439
self.assertIn(

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@ All notable changes to EFM (Evidence-First Memory for Claude Code) will be docum
44

55
---
66

7+
## 2026-03-03 — V3.2 Phase 7: Worktree-Safe Hook Prefix
8+
9+
### Fix: Hooks fail when Claude Code is opened from a git worktree
10+
11+
**BUG: `--show-toplevel` returns the worktree path, not the main repo path**
12+
`git rev-parse --show-toplevel` in a worktree returns the worktree directory (e.g. `.claude/worktrees/vibrant-swartz/`), not the main repository root. This caused all hook scripts that reference `.memory/hooks/` to fail with "No such file or directory" because the worktree directory doesn't contain a `.memory/` tree.
13+
14+
Root cause discovered while using Claude Code worktrees in the QTP project. EFM itself had the identical bug.
15+
16+
**Fix:** Changed the hook command prefix in `generate_hooks_settings()` to:
17+
```
18+
_r="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || exit 0; _r="${_r%/.git}"; cd "$_r" && ...
19+
```
20+
- `--git-common-dir` always returns the main repo's `.git` path (not the worktree-specific subdir)
21+
- `--path-format=absolute` ensures an absolute path regardless of CWD
22+
- `${_r%/.git}` strips the trailing `/.git` using bash suffix removal (no sed — avoids false matches on paths containing `.github` directories)
23+
- Requires git 2.31+ (March 2021)
24+
25+
All 5 hooks (SessionStart, PreToolUse:Edit/Write, PreToolUse:EnterPlanMode, Stop, PreCompact) updated.
26+
27+
**Upgrade:** Run `/memory-init` to regenerate hooks with the worktree-safe prefix.
28+
29+
**Modified files (2):**
30+
- `.memory/lib/init.py``generate_hooks_settings()` worktree-safe prefix (with explanatory comment)
31+
- `.memory/tests/test_init.py` — updated assertion to check for `--git-common-dir` instead of `--show-toplevel`
32+
33+
---
34+
735
## 2026-02-17 — V3.2 Phase 6: Git Merge Safety (`/memory-repair`)
836

937
### New: Post-merge repair command for `events.jsonl`

0 commit comments

Comments
 (0)