Skip to content

Fix worktree suffix project root detection#435

Open
ken-jo wants to merge 7 commits intomksglu:nextfrom
ken-jo:fix/worktree-project-root-suffix
Open

Fix worktree suffix project root detection#435
ken-jo wants to merge 7 commits intomksglu:nextfrom
ken-jo:fix/worktree-project-root-suffix

Conversation

@ken-jo
Copy link
Copy Markdown

@ken-jo ken-jo commented May 5, 2026

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 used process.cwd() directly and ran git commands without -C <projectDir>. That breaks in two ways:

  • Installed MCP startup can chdir into the package directory, so server-side DB paths can be computed from the plugin install location instead of the user's repo.
  • Hook scripts parse the real project cwd from stdin, but getSessionDBPath(), getSessionEventsPath(), and getCleanupFlagPath() did not accept that project directory, so Codex hooks still fell back to process cwd.
  • Comparing raw cwd with the first git worktree list --porcelain entry misclassifies /main-worktree/subdir as a linked worktree and makes /linked-worktree/subdir hash differently from /linked-worktree.

Fix

  • Change getWorktreeSuffix(projectDir = process.cwd()) to resolve the current git worktree root via git -C <projectDir> rev-parse --show-toplevel.
  • Compare that normalized root against the main worktree root from git -C <projectDir> worktree list --porcelain.
  • Hash the resolved current worktree root for linked worktree suffixes, making the suffix stable from any subdirectory in the same worktree.
  • Pass the parsed Codex hook cwd into session DB/events/cleanup path helpers.
  • Update server-side timeline/stats/purge path computation to pass the resolved project directory into suffix calculation.
  • Rebuild shipped bundles (server.bundle.mjs, cli.bundle.mjs, hooks/session-db.bundle.mjs).

Tests

  • pnpm run build
  • pnpm run typecheck
  • pnpm vitest run tests/worktree-suffix.test.ts
  • pnpm vitest run tests/worktree-suffix.test.ts tests/session-hooks-smoke.test.ts
  • pnpm vitest run tests/hooks/integration.test.ts
  • pnpm vitest run tests/core/server-timeline-adapter.test.ts tests/worktree-suffix.test.ts tests/session-hooks-smoke.test.ts tests/hooks/integration.test.ts

I also ran pnpm test. It still has environment-dependent failures on this macOS sandbox:

  • local server bind denied with listen EPERM 127.0.0.1
  • broken local Go stdlib installation
  • several server path-resolution tests unable to open/create DB files under the sandboxed home

The targeted worktree/session/hook/server timeline coverage above passes after the fix.

Fixes #434

Reported-by: jo.dreame@gmail.com

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-toplevel and compare against the main worktree root.
  • Thread projectDir through 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.

Comment thread src/session/db.ts
Comment on lines +34 to +36
function normalizeWorktreePath(path: string): string {
return path.replace(/\\/g, "/").replace(/\/+$/, "");
}
Comment thread hooks/session-helpers.mjs Outdated
}

function workTreeMarkerPath(projectDir) {
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
Comment thread hooks/session-helpers.mjs
Comment on lines +254 to 260
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`);
}
Comment thread hooks/session-helpers.mjs Outdated
function workTreeMarkerPath(cwd) {
const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
function normalizeWorktreePath(path) {
return path.replace(/\\/g, "/").replace(/\/+$/, "");
@mksglu mksglu changed the base branch from main to next May 5, 2026 20:39
@mksglu
Copy link
Copy Markdown
Owner

mksglu commented May 5, 2026

Hi! Did you test that? Let's be sure also it's working on Windows, Linux and Mac. @ken-jo

Copy link
Copy Markdown

@EPPCOM-Solutions EPPCOM-Solutions left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-toplevel eliminates subdirectory misclassification
  • Codex hook entry points now pass projectDir from 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.

@ken-jo
Copy link
Copy Markdown
Author

ken-jo commented May 6, 2026

Pushed follow-up commit c62d96c: fix: normalize project paths for session helpers.

What changed:

  • Preserves filesystem roots during worktree path normalization (for example / and C:/) instead of trimming them into ambiguous values.
  • Normalizes hook-side project paths before hashing DB/events/cleanup paths, so Windows \ vs / separators and trailing slashes produce the same session filenames.
  • Normalizes the hook cross-process worktree marker key for the same reason.
  • Uses projectDirOverride ?? getProjectDir(opts) so an explicit empty-string override is not treated as missing.
  • Added regression coverage for hook session path normalization and updated the server hash helper assertion.
  • Rebuilt shipped bundles: server.bundle.mjs, cli.bundle.mjs, and hooks/session-db.bundle.mjs.

Verification:

  • Windows PowerShell: pnpm run typecheck
  • Windows PowerShell: pnpm vitest run tests/worktree-suffix.test.ts tests/hooks/integration.test.ts tests/core/server-timeline-adapter.test.ts tests/session-hooks-smoke.test.ts ✅ (93 tests)
  • Windows PowerShell: pnpm vitest run tests/core/server.test.ts -t "Project dir hash consistency"
  • Windows PowerShell: pnpm run build
  • WSL Ubuntu 24.04: same typecheck / targeted tests / hash consistency / build ✅

@ken-jo
Copy link
Copy Markdown
Author

ken-jo commented May 6, 2026

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 :)

@mksglu
Copy link
Copy Markdown
Owner

mksglu commented May 7, 2026

#434 Let's resolve comments and conflicts. The PR is so important for us. Affected so many core parts in. Did you test on manually? Then I'll merge. It's HUGE!. Go. @ken-jo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Worktree isolation uses process cwd instead of project worktree root

4 participants