fix: use cwd instead of CLAUDE_CONFIG_DIR for observer session isolation (#845)

The previous approach (PR #837) set CLAUDE_CONFIG_DIR to isolate observer
sessions from `claude --resume`. However, this broke authentication because
Claude Code stores credentials in the config directory.

This fix uses the SDK's `cwd` option instead:
- Observer sessions run with cwd=~/.claude-mem/observer-sessions/
- Project name = path.basename(cwd) = "observer-sessions"
- Sessions won't appear when running `claude --resume` from actual projects
- Authentication works because ~/.claude/ config is preserved

Changes:
- ProcessRegistry.ts: Remove CLAUDE_CONFIG_DIR override from spawn
- SDKAgent.ts: Add cwd option to query() pointing to observer dir
- paths.ts: Rename OBSERVER_CONFIG_DIR to OBSERVER_SESSIONS_DIR

Fixes regression from #837

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-01-28 16:18:15 -05:00
committed by GitHub
parent c948a7778b
commit abffce6424
5 changed files with 42 additions and 48 deletions
+3 -14
View File
@@ -19,7 +19,6 @@
import { spawn, exec, ChildProcess } from 'child_process';
import { promisify } from 'util';
import { logger } from '../../utils/logger.js';
import { OBSERVER_CONFIG_DIR, ensureDir } from '../../shared/paths.js';
const execAsync = promisify(exec);
@@ -189,14 +188,10 @@ export async function reapOrphanedProcesses(activeSessionIds: Set<number>): Prom
* The SDK's spawnClaudeCodeProcess option allows us to intercept subprocess
* creation and capture the PID before the SDK hides it.
*
* IMPORTANT (Issue #832): We set CLAUDE_CONFIG_DIR to isolate observer sessions.
* This prevents observer sessions from appearing in `claude --resume` list,
* which was causing 34%+ of resume entries to be internal plugin sessions.
* NOTE: Session isolation is handled via the `cwd` option in SDKAgent.ts,
* NOT via CLAUDE_CONFIG_DIR (which breaks authentication).
*/
export function createPidCapturingSpawn(sessionDbId: number) {
// Ensure observer config directory exists
ensureDir(OBSERVER_CONFIG_DIR);
return (spawnOptions: {
command: string;
args: string[];
@@ -204,15 +199,9 @@ export function createPidCapturingSpawn(sessionDbId: number) {
env?: NodeJS.ProcessEnv;
signal?: AbortSignal;
}) => {
// Inject CLAUDE_CONFIG_DIR to isolate observer sessions (Issue #832)
const isolatedEnv = {
...spawnOptions.env,
CLAUDE_CONFIG_DIR: OBSERVER_CONFIG_DIR
};
const child = spawn(spawnOptions.command, spawnOptions.args, {
cwd: spawnOptions.cwd,
env: isolatedEnv,
env: spawnOptions.env,
stdio: ['pipe', 'pipe', 'pipe'],
signal: spawnOptions.signal, // CRITICAL: Pass signal for AbortController integration
windowsHide: true
+6 -1
View File
@@ -16,7 +16,7 @@ import { SessionManager } from './SessionManager.js';
import { logger } from '../../utils/logger.js';
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js';
import { SettingsDefaultsManager } from '../../shared/SettingsDefaultsManager.js';
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
import { USER_SETTINGS_PATH, OBSERVER_SESSIONS_DIR, ensureDir } from '../../shared/paths.js';
import type { ActiveSession, SDKUserMessage } from '../worker-types.js';
import { ModeManager } from '../domain/ModeManager.js';
import { processAgentResponse, type WorkerRef } from './agents/index.js';
@@ -101,10 +101,15 @@ export class SDKAgent {
// Run Agent SDK query loop
// Only resume if we have a captured memory session ID
// Use custom spawn to capture PIDs for zombie process cleanup (Issue #737)
// Use dedicated cwd to isolate observer sessions from user's `claude --resume` list
ensureDir(OBSERVER_SESSIONS_DIR);
const queryResult = query({
prompt: messageGenerator,
options: {
model: modelId,
// Isolate observer sessions - they'll appear under project "observer-sessions"
// instead of polluting user's actual project resume lists
cwd: OBSERVER_SESSIONS_DIR,
// Only resume if BOTH: (1) we have a memorySessionId AND (2) this isn't the first prompt
// On worker restart, memorySessionId may exist from a previous SDK session but we
// need to start fresh since the SDK context was lost
+3 -3
View File
@@ -38,9 +38,9 @@ export const USER_SETTINGS_PATH = join(DATA_DIR, 'settings.json');
export const DB_PATH = join(DATA_DIR, 'claude-mem.db');
export const VECTOR_DB_DIR = join(DATA_DIR, 'vector-db');
// Isolated config directory for observer sessions (Issue #832)
// This prevents observer sessions from appearing in `claude --resume` list
export const OBSERVER_CONFIG_DIR = join(DATA_DIR, 'observer-config');
// Observer sessions directory - used as cwd for SDK queries
// Sessions here won't appear in user's `claude --resume` for their actual projects
export const OBSERVER_SESSIONS_DIR = join(DATA_DIR, 'observer-sessions');
// Claude integration paths
export const CLAUDE_SETTINGS_PATH = join(CLAUDE_CONFIG_DIR, 'settings.json');