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

* fix: use parent project name for worktree observation writes (#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
#1081, #1317, and #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.

* fix: address review nitpicks — allProjects fallback, JSDoc, write-path 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

* refactor(project-name): rename local to cwdProjectName and dedupe allProjects

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.

---------

Co-authored-by: Ethan Hurst <ethan.hurst@outlook.com.au>
This commit is contained in:
Ethan
2026-04-15 17:58:14 +10:00
committed by GitHub
parent 3d92684e04
commit 16a0737dfc
6 changed files with 69 additions and 19 deletions
+5 -4
View File
@@ -10,7 +10,7 @@ import { homedir } from 'os';
import { unlinkSync } from 'fs';
import { SessionStore } from '../sqlite/SessionStore.js';
import { logger } from '../../utils/logger.js';
import { getProjectName } from '../../utils/project-name.js';
import { getProjectContext } from '../../utils/project-name.js';
import type { ContextInput, ContextConfig, Observation, SessionSummary } from './types.js';
import { loadContextConfig } from './ContextConfigLoader.js';
@@ -129,11 +129,12 @@ export async function generateContext(
): Promise<string> {
const config = loadContextConfig();
const cwd = input?.cwd ?? process.cwd();
const project = getProjectName(cwd);
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];
// Use provided projects array (for worktree support) or fall back to all known projects
const projects = input?.projects ?? context.allProjects;
// Full mode: fetch all observations but keep normal rendering (level 1 summaries)
if (input?.full) {
+2 -2
View File
@@ -4,7 +4,7 @@ import { fileEditHandler } from '../../cli/handlers/file-edit.js';
import { sessionCompleteHandler } from '../../cli/handlers/session-complete.js';
import { ensureWorkerRunning, workerHttpRequest } from '../../shared/worker-utils.js';
import { logger } from '../../utils/logger.js';
import { getProjectContext, getProjectName } from '../../utils/project-name.js';
import { getProjectContext } from '../../utils/project-name.js';
import { writeAgentsMd } from '../../utils/agents-md-utils.js';
import { resolveFieldSpec, resolveFields, matchesRule } from './field-utils.js';
import { expandHomePath } from './config.js';
@@ -104,7 +104,7 @@ export class TranscriptEventProcessor {
const resolved = resolveFieldSpec(fieldSpec, entry, ctx);
if (typeof resolved === 'string' && resolved.trim()) return resolved;
if (watch.project) return watch.project;
if (session.cwd) return getProjectName(session.cwd);
if (session.cwd) return getProjectContext(session.cwd).primary;
return session.project;
}
@@ -22,7 +22,7 @@ import { PrivacyCheckValidator } from '../../validation/PrivacyCheckValidator.js
import { SettingsDefaultsManager } from '../../../../shared/SettingsDefaultsManager.js';
import { USER_SETTINGS_PATH } from '../../../../shared/paths.js';
import { getProcessBySession, ensureProcessExit } from '../../ProcessRegistry.js';
import { getProjectName } from '../../../../utils/project-name.js';
import { getProjectContext } from '../../../../utils/project-name.js';
import { normalizePlatformSource } from '../../../../shared/platform-source.js';
export class SessionRoutes extends BaseRouteHandler {
@@ -507,7 +507,7 @@ export class SessionRoutes extends BaseRouteHandler {
private handleObservationsByClaudeId = this.wrapHandler((req: Request, res: Response): void => {
const { contentSessionId, tool_name, tool_input, tool_response, cwd } = req.body;
const platformSource = normalizePlatformSource(req.body.platformSource);
const project = typeof cwd === 'string' && cwd.trim() ? getProjectName(cwd) : '';
const project = typeof cwd === 'string' && cwd.trim() ? getProjectContext(cwd).primary : '';
if (!contentSessionId) {
return this.badRequest(res, 'Missing contentSessionId');