Fix worktree suffix project root detection#435
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes worktree session isolation by deriving the session suffix from the actual git worktree root of the target project directory (instead of process.cwd()), and ensures Codex hooks pass the parsed project cwd through to session DB/events/cleanup path helpers so server and hooks agree on the same session storage.
Changes:
- Update worktree suffix resolution to use
git -C <projectDir> rev-parse --show-topleveland compare against the main worktree root. - Thread
projectDirthrough server session DB path construction and Codex hook session path helpers. - Add regression tests covering main-vs-linked worktree behavior and the updated server wiring.
Reviewed changes
Copilot reviewed 10 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/worktree-suffix.test.ts | Adds an integration-style test creating a main + linked git worktree and asserting stable suffix behavior from subdirectories. |
| tests/core/server-timeline-adapter.test.ts | Updates the assertion to ensure server timeline mode uses projectDir for both project hashing and worktree suffix. |
| src/session/db.ts | Changes getWorktreeSuffix to accept projectDir and resolve suffix from git worktree roots; adds normalization helpers and a test reset hook. |
| src/server.ts | Passes projectDir into hashProjectDir() / getWorktreeSuffix() when constructing session DB paths used by timeline/stats/purge. |
| hooks/session-helpers.mjs | Mirrors the new worktree-root-based suffix logic for hooks and adds optional projectDirOverride to session path helpers. |
| hooks/session-db.bundle.mjs | Rebuilds the shipped hook bundle to include the updated suffix logic. |
| hooks/codex/userpromptsubmit.mjs | Passes projectDir into getSessionDBPath so Codex hook uses the correct worktree-derived DB. |
| hooks/codex/stop.mjs | Passes projectDir into getSessionDBPath so Codex stop events land in the correct per-worktree DB. |
| hooks/codex/sessionstart.mjs | Passes projectDir into DB/events/cleanup path helpers to keep Codex resume/compact/startup aligned to the worktree root. |
| hooks/codex/posttooluse.mjs | Passes projectDir into getSessionDBPath for correct per-worktree attribution/event persistence. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function normalizeWorktreePath(path: string): string { | ||
| return path.replace(/\\/g, "/").replace(/\/+$/, ""); | ||
| } |
| } | ||
|
|
||
| function workTreeMarkerPath(projectDir) { | ||
| const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16); |
| export function getSessionDBPath(opts = CLAUDE_OPTS, projectDirOverride) { | ||
| const projectDir = projectDirOverride || getProjectDir(opts); | ||
| const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16); | ||
| const dir = join(resolveConfigDir(opts), "context-mode", "sessions"); | ||
| mkdirSync(dir, { recursive: true }); | ||
| return join(dir, `${hash}${getWorktreeSuffix()}.db`); | ||
| return join(dir, `${hash}${getWorktreeSuffix(projectDir)}.db`); | ||
| } |
| function workTreeMarkerPath(cwd) { | ||
| const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16); | ||
| function normalizeWorktreePath(path) { | ||
| return path.replace(/\\/g, "/").replace(/\/+$/, ""); |
|
Hi! Did you test that? Let's be sure also it's working on Windows, Linux and Mac. @ken-jo |
EPPCOM-Solutions
left a comment
There was a problem hiding this comment.
NEEDS_CHANGE (minor) — awaiting Windows confirmation + one-line fix
The fix is logically correct and complete. Both root causes from #434 are addressed:
git -C <projectDir> rev-parse --show-topleveleliminates subdirectory misclassification- Codex hook entry points now pass
projectDirfrom stdin into all session path helpers - Tests cover main-worktree subdirectory stability and linked-worktree suffix stability
Two items before merge:
1. || vs ?? latent bug (one-line fix)
In the session path helpers, projectDirOverride || getProjectDir(opts) will silently fall through if projectDirOverride is "" (empty string = falsy). Should be ??:
// before
projectDirOverride || getProjectDir(opts)
// after
projectDirOverride ?? getProjectDir(opts)In practice, Codex hook stdin never sends an empty string — but defensive code is better here.
2. Cross-platform confirmation (per maintainer request)
@ken-jo — can you confirm test results on Windows and Linux? Specifically pnpm vitest run tests/worktree-suffix.test.ts on each platform. The normalizeWorktreePath() Windows backslash handling looks correct but needs empirical confirmation.
Everything else is ready.
|
Pushed follow-up commit What changed:
Verification:
|
|
Hi @mksglu! Thanks for the quick feedback. Apologies — I had only tested the original change on macOS. I’ve now tested it on Windows and WSL Ubuntu, and everything is clean on the targeted checks. I also pushed a small follow-up for the path-normalization review feedback. Please let me know if anything still looks off. P.S. I don’t have a dedicated Linux machine available, so I used WSL Ubuntu as the Linux check. Hope that’s okay. Thanks again for building such a great project :) |
Summary
Fix worktree session isolation so it is derived from the actual project worktree root, not the MCP server or hook process cwd.
Root Cause
getWorktreeSuffix()previously usedprocess.cwd()directly and ran git commands without-C <projectDir>. That breaks in two ways:chdirinto the package directory, so server-side DB paths can be computed from the plugin install location instead of the user's repo.cwdfrom stdin, butgetSessionDBPath(),getSessionEventsPath(), andgetCleanupFlagPath()did not accept that project directory, so Codex hooks still fell back to process cwd.git worktree list --porcelainentry misclassifies/main-worktree/subdiras a linked worktree and makes/linked-worktree/subdirhash differently from/linked-worktree.Fix
getWorktreeSuffix(projectDir = process.cwd())to resolve the current git worktree root viagit -C <projectDir> rev-parse --show-toplevel.git -C <projectDir> worktree list --porcelain.cwdinto session DB/events/cleanup path helpers.server.bundle.mjs,cli.bundle.mjs,hooks/session-db.bundle.mjs).Tests
pnpm run buildpnpm run typecheckpnpm vitest run tests/worktree-suffix.test.tspnpm vitest run tests/worktree-suffix.test.ts tests/session-hooks-smoke.test.tspnpm vitest run tests/hooks/integration.test.tspnpm vitest run tests/core/server-timeline-adapter.test.ts tests/worktree-suffix.test.ts tests/session-hooks-smoke.test.ts tests/hooks/integration.test.tsI also ran
pnpm test. It still has environment-dependent failures on this macOS sandbox:listen EPERM 127.0.0.1The targeted worktree/session/hook/server timeline coverage above passes after the fix.
Fixes #434
Reported-by: jo.dreame@gmail.com