Skip to content

fix: use parent project name for worktree observation writes#1820

Merged
thedotmack merged 3 commits intothedotmack:mainfrom
0xLeathery:fix/worktree-write-path-project-name
Apr 15, 2026
Merged

fix: use parent project name for worktree observation writes#1820
thedotmack merged 3 commits intothedotmack:mainfrom
0xLeathery:fix/worktree-write-path-project-name

Conversation

@0xLeathery
Copy link
Copy Markdown
Contributor

Fixes #1819

Summary

  • getProjectContext().primary now returns parentProjectName when in a worktree (instead of basename(cwd))
  • All four write-path call sites switched from getProjectName() to getProjectContext().primary
  • Regression test creates a real worktree directory structure and asserts primary === parentProjectName

Problem

Observations and sessions from git worktrees are stored under basename(cwd) (e.g. modest-burnell) instead of the parent repo name (e.g. my-app). This is the same root cause as #1081, #1317, and #1500 — it regresses because getProjectName() (not worktree-aware) and getProjectContext() (worktree-aware) coexist, and write paths use the wrong one.

Root Cause

Operation Before Worktree-aware?
Write observations getProjectName(cwd) No
Write sessions getProjectName(cwd) No
Read context injection getProjectContext(cwd) Yes

Changes

src/utils/project-name.tsgetProjectContext() now sets primary: worktreeInfo.parentProjectName when a worktree is detected, instead of primary: basename(cwd).

4 call site migrations (all getProjectName(cwd)getProjectContext(cwd).primary):

  • src/cli/handlers/session-init.ts — session init hook
  • src/services/worker/http/routes/SessionRoutes.ts — observation handler
  • src/services/context/ContextBuilder.ts — context generation
  • src/services/transcripts/processor.ts — transcript processing

tests/utils/project-name.test.ts — regression test that creates a temporary worktree directory structure (.git file with gitdir: pointer) and asserts:

  • ctx.isWorktree === true
  • ctx.primary === 'main-repo' (parent name, not worktree name)
  • ctx.allProjects === ['main-repo', 'my-worktree']

Why this keeps regressing

Each prior fix patched individual call sites, but getProjectName() still exists as the simpler, easier-to-reach function. New code naturally gravitates toward it. This PR addresses the core issue: getProjectContext().primary now returns the correct name for worktrees, so even if callers use either function path, the worktree-aware one gives the right answer. The regression test provides a safety net against future changes.

Test plan

  • bun test tests/utils/project-name.test.ts — 16/16 pass (including new worktree regression test)
  • Manual verification: run Claude Code session from a git worktree, confirm observations stored under parent project name

…ack#1819)

Observations and sessions from git worktrees were stored under
basename(cwd) instead of the parent repo name because write paths
called getProjectName() (not worktree-aware) instead of
getProjectContext() (worktree-aware). This is the same bug as
thedotmack#1081, thedotmack#1317, and thedotmack#1500 — it regressed because the two functions
coexist and new code reached for the simpler one.

Fix: getProjectContext() now returns parentProjectName as primary
when in a worktree, and all four write-path call sites now use
getProjectContext().primary instead of getProjectName().

Includes regression test that creates a real worktree directory
structure and asserts primary === parentProjectName.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a7c6bff-9a1b-4597-ab55-dd63b28be616

📥 Commits

Reviewing files that changed from the base of the PR and between 2089c75 and ce61db3.

📒 Files selected for processing (1)
  • src/utils/project-name.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/project-name.ts

Summary by CodeRabbit

  • Bug Fixes

    • Project derivation is now consistent: the canonical parent project is used for writes/queries and observation mapping, resolving mismatches in session init, observation handling, and context generation.
    • Worktree-aware context now falls back to a multi-project list when appropriate, improving query behavior across multi-project setups.
  • Tests

    • Added tests validating git worktree detection and that the parent repository is used as the primary project for writes/queries.

Walkthrough

The PR standardizes project resolution to use getProjectContext(cwd).primary instead of getProjectName(cwd) across CLI session init, context generation, transcript processing, and session/observation routing. getProjectContext now returns the parent repository as primary for git worktrees so observations and sessions map to the parent project.

Changes

Cohort / File(s) Summary
CLI & Server Call Sites
src/cli/handlers/session-init.ts, src/services/transcripts/processor.ts, src/services/worker/http/routes/SessionRoutes.ts
Replaced getProjectName(cwd) with getProjectContext(cwd).primary; updated imports and local project variable derivation used when initializing sessions and creating SDK sessions.
Context Generation
src/services/context/ContextBuilder.ts
Uses getProjectContext(cwd).primary for project; when input?.projects is absent, projects defaults to context.allProjects (multi-project path) instead of single-element [project].
Project-name Utility
src/utils/project-name.ts
Adjusted getProjectContext and ProjectContext docs so primary is the canonical project for writes/queries (parent repo for worktrees); allProjects now lists [parentRepo, worktreeName] and primary is the parent repo when in a worktree.
Tests
tests/utils/project-name.test.ts
Added worktree-focused tests that create a simulated git worktree and assert isWorktree, primary (parent repo), parent, and allProjects contents; includes setup/teardown.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (session-init)
  participant Context as ContextService
  participant API as Server (SessionRoutes)
  participant DB as Observations DB

  CLI->>Context: getProjectContext(cwd)
  Context-->>CLI: { primary, parent, allProjects }
  CLI->>API: POST /api/sessions/init (project=primary)
  API->>Context: getProjectContext(cwd)
  Context-->>API: { primary, parent, allProjects }
  API->>DB: createSDKSession(contentSessionId, project=primary) / queueObservation(project=primary,...)
  DB-->>API: ack
  API-->>CLI: session init response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • thedotmack/claude-mem#1500 — Addresses the same regression where worktree observations were tagged with the worktree directory name; this PR replaces getProjectName usages with getProjectContext(...).primary.

Possibly related PRs

Poem

🐰 Hopping through branches, I peek and see,

Parent names calling, not the worktree of me.
Notes now nest rightly where histories belong,
No more stray tags where they once went wrong.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: switching to parent project name for worktree observation writes, which is the main objective of the PR.
Description check ✅ Passed The description provides a comprehensive explanation of the problem, root cause, changes, and test plan, all directly related to the changeset.
Linked Issues check ✅ Passed The PR fully addresses #1819 by implementing all three suggested durable fixes: making getProjectContext().primary worktree-aware, migrating all four write-path call sites, and adding regression tests covering worktree scenarios.
Out of Scope Changes check ✅ Passed All changes (five modified source files, one test file) are directly scoped to fixing worktree project name resolution across write and read paths, with no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/services/context/ContextBuilder.ts (1)

132-136: Consider defaulting to context.allProjects when input.projects is not provided.

With Line 132 now resolving to parent repo in worktrees, using context.allProjects as the default fallback can keep context reads compatible with legacy worktree-labeled records during transition.

♻️ Suggested adjustment
-  const project = getProjectContext(cwd).primary;
+  const context = getProjectContext(cwd);
+  const project = context.primary;
   const platformSource = input?.platform_source;

   // Use provided projects array (for worktree support) or fall back to single project
-  const projects = input?.projects || [project];
+  const projects = input?.projects ?? context.allProjects;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/context/ContextBuilder.ts` around lines 132 - 136, Change the
fallback for projects so that when input?.projects is undefined you default to
using context.allProjects rather than [project]; update the assignment that
currently builds projects (where getProjectContext(cwd).primary and
platformSource are used) to prefer input.projects and otherwise use
context.allProjects to preserve compatibility with legacy worktree-labeled
records; ensure the symbol names involved are getProjectContext, project,
projects, input.projects and context.allProjects so the change is applied in the
same expression that constructs the projects array.
src/utils/project-name.ts (1)

91-95: Refresh ProjectContext docs to match the new canonical primary behavior.

Line 94 now sets primary to the parent repo in worktrees, but the interface/JSDoc above still read like primary may be the worktree name. Tightening those comments will prevent future caller misuse.

📝 Suggested doc-only update
 export interface ProjectContext {
-  /** The current project name (worktree or main repo) */
+  /** Canonical project name for writes/primary queries (parent repo in worktrees) */
   primary: string;
   /** Parent project name if in a worktree, null otherwise */
   parent: string | null;
   /** True if currently in a worktree */
   isWorktree: boolean;
-  /** All projects to query: [primary] for main repo, [parent, primary] for worktree */
+  /** All projects to query: [primary] for main repo, [parentRepo, worktreeName] for worktree */
   allProjects: string[];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/project-name.ts` around lines 91 - 95, Update the ProjectContext
JSDoc/interface to reflect that in worktrees the canonical "primary" is the
parent repository name (not the worktree name): modify the comment for
ProjectContext and the "primary" field to state it will be the parent project
when worktreeInfo is present, and clarify "parent" remains the parent project
name as well; locate the ProjectContext declaration/JSDoc near the top of
src/utils/project-name.ts and update wording so callers understand primary
references the main repo in worktree scenarios (mention
worktreeInfo.parentProjectName and the functions that return it, e.g., where
primary is assigned).
tests/utils/project-name.test.ts (1)

100-133: Add one integration regression test to protect write-path call sites.

This test validates resolver behavior well, but the historical regressions came from write callers using the wrong helper. Add one integration test that performs a worktree write flow (e.g., /api/sessions/init or observation path) and asserts the persisted project is the parent repo name.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/services/context/ContextBuilder.ts`:
- Around line 132-136: Change the fallback for projects so that when
input?.projects is undefined you default to using context.allProjects rather
than [project]; update the assignment that currently builds projects (where
getProjectContext(cwd).primary and platformSource are used) to prefer
input.projects and otherwise use context.allProjects to preserve compatibility
with legacy worktree-labeled records; ensure the symbol names involved are
getProjectContext, project, projects, input.projects and context.allProjects so
the change is applied in the same expression that constructs the projects array.

In `@src/utils/project-name.ts`:
- Around line 91-95: Update the ProjectContext JSDoc/interface to reflect that
in worktrees the canonical "primary" is the parent repository name (not the
worktree name): modify the comment for ProjectContext and the "primary" field to
state it will be the parent project when worktreeInfo is present, and clarify
"parent" remains the parent project name as well; locate the ProjectContext
declaration/JSDoc near the top of src/utils/project-name.ts and update wording
so callers understand primary references the main repo in worktree scenarios
(mention worktreeInfo.parentProjectName and the functions that return it, e.g.,
where primary is assigned).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 984c7a38-6477-437b-84a9-9620bca61017

📥 Commits

Reviewing files that changed from the base of the PR and between cde4faa and d0096ff.

📒 Files selected for processing (6)
  • src/cli/handlers/session-init.ts
  • src/services/context/ContextBuilder.ts
  • src/services/transcripts/processor.ts
  • src/services/worker/http/routes/SessionRoutes.ts
  • src/utils/project-name.ts
  • tests/utils/project-name.test.ts

coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 14, 2026
…h test

- ContextBuilder: default projects to context.allProjects for legacy
  worktree-labeled record compatibility
- ProjectContext: clarify JSDoc that primary is canonical (parent repo
  in worktrees)
- Tests: add write-path regression test mirroring session-init/SessionRoutes
  pattern; refactor worktree fixture into beforeAll/afterAll
@0xLeathery
Copy link
Copy Markdown
Contributor Author

Addressed all three nitpicks in 2089c75:

  1. ContextBuilder.tsallProjects fallback: Default now uses context.allProjects instead of [project], so context reads in worktrees also pick up legacy worktree-labeled records during transition.

  2. ProjectContext JSDoc: Updated primary doc to "Canonical project name for writes/queries (parent repo in worktrees)" and allProjects to "[parentRepo, worktreeName] for worktree" — should prevent future caller misuse.

  3. Write-path regression test: Added a test that mirrors the exact getProjectContext(cwd).primary pattern used by session-init.ts and SessionRoutes.ts, asserting it resolves to the parent repo. Refactored worktree fixtures into beforeAll/afterAll to share across both tests. All 17 tests pass.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/utils/project-name.ts (1)

91-98: Consider a small readability hardening in the worktree return path.

This is functionally correct, but renaming the local primary (Line 81) to cwdProjectName and deduping allProjects would make future regressions less likely.

♻️ Proposed refactor
 export function getProjectContext(cwd: string | null | undefined): ProjectContext {
-  const primary = getProjectName(cwd);
+  const cwdProjectName = getProjectName(cwd);

   if (!cwd) {
-    return { primary, parent: null, isWorktree: false, allProjects: [primary] };
+    return { primary: cwdProjectName, parent: null, isWorktree: false, allProjects: [cwdProjectName] };
   }
@@
   if (worktreeInfo.isWorktree && worktreeInfo.parentProjectName) {
@@
+    const allProjects = Array.from(new Set([worktreeInfo.parentProjectName, cwdProjectName]));
     return {
       primary: worktreeInfo.parentProjectName,
       parent: worktreeInfo.parentProjectName,
       isWorktree: true,
-      allProjects: [worktreeInfo.parentProjectName, primary]
+      allProjects
     };
   }

-  return { primary, parent: null, isWorktree: false, allProjects: [primary] };
+  return { primary: cwdProjectName, parent: null, isWorktree: false, allProjects: [cwdProjectName] };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/project-name.ts` around lines 91 - 98, The worktree return uses a
local variable named primary which is confusing; rename that local variable to
cwdProjectName (the value coming from the current working dir) and update the
return to use cwdProjectName for the cwd-specific slot and
worktreeInfo.parentProjectName for the parent slot, then dedupe the allProjects
array (e.g., build allProjects from [worktreeInfo.parentProjectName,
cwdProjectName] and remove duplicates) so duplicate entries are not returned;
update references to primary in this function accordingly (look for primary and
worktreeInfo in this module).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/project-name.ts`:
- Around line 91-98: The worktree return uses a local variable named primary
which is confusing; rename that local variable to cwdProjectName (the value
coming from the current working dir) and update the return to use cwdProjectName
for the cwd-specific slot and worktreeInfo.parentProjectName for the parent
slot, then dedupe the allProjects array (e.g., build allProjects from
[worktreeInfo.parentProjectName, cwdProjectName] and remove duplicates) so
duplicate entries are not returned; update references to primary in this
function accordingly (look for primary and worktreeInfo in this module).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6abbe31-075c-499a-8330-19bbc7543de4

📥 Commits

Reviewing files that changed from the base of the PR and between d0096ff and 2089c75.

📒 Files selected for processing (3)
  • src/services/context/ContextBuilder.ts
  • src/utils/project-name.ts
  • tests/utils/project-name.test.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/utils/project-name.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/context/ContextBuilder.ts

coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 14, 2026
…Projects

Addresses final CodeRabbit nitpick: disambiguates the local variable
from the returned `primary` field, and dedupes allProjects via Set
in case parent and cwd resolve to the same name.
@thedotmack thedotmack merged commit 16a0737 into thedotmack:main Apr 15, 2026
1 check passed
thedotmack added a commit that referenced this pull request Apr 16, 2026
…t cross worktrees

Revert of #1820 behavior. Each worktree now gets its own bucket:
- In a worktree, primary = `parent/worktree` (e.g. `claude-mem/dar-es-salaam`)
- In a main repo, primary = basename (unchanged)
- allProjects is always `[primary]` — strict isolation at query time

Includes a one-off maintenance script (scripts/worktree-remap.ts) that
retroactively reattributes past sessions to their worktree using path
signals in observations and user prompts. Two-rule inference keeps the
remap high-confidence:
  1. The worktree basename in the path matches the session's current
     plain project name (pre-#1820 era; trusted).
  2. Or all worktree path signals converge on a single (parent, worktree)
     across the session.
Ambiguous sessions are skipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

Regression: worktree observations tagged with worktree dir name in v12.1.0 (#1081 / #1500)

2 participants