thedotmack/claude-mem · warning
Missing cwd in PostToolUse hook input for session ${sessionI
Error message
Missing cwd in PostToolUse hook input for session ${sessionId}, tool ${toolName} What it means
ProcessRegistry persists managed-process records to a JSON registry file and reloads them at startup. JSON.parse of registryPath threw — the file is missing (readFileSync ENOENT is caught by the same block), truncated, or not JSON. The handler warns, clears entries, then prunes dead PIDs and persists a rebuilt registry, so the system self-heals at the cost of losing tracked runtime handles.
Source
Thrown at src/cli/handlers/observation.ts:55
logger.debug('HOOK', 'Observation sent successfully via worker', { toolName: input.toolName });
return { continue: true, suppressOutput: true };
}
export const observationHandler: EventHandler = {
async execute(input: NormalizedHookInput): Promise<HookResult> {
const { sessionId, cwd, toolName, toolInput, toolResponse } = input;
const platformSource = normalizePlatformSource(input.platform);
if (!toolName) {
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
const toolStr = logger.formatTool(toolName, toolInput);
logger.dataIn('HOOK', `PostToolUse: ${toolStr}`, {});
if (!cwd) {
throw new Error(`Missing cwd in PostToolUse hook input for session ${sessionId}, tool ${toolName}`);
}
if (!shouldTrackProject(cwd)) {
logger.debug('HOOK', 'Project excluded from tracking, skipping observation', { cwd, toolName });
return { continue: true, suppressOutput: true };
}
const runtime = resolveRuntimeContext();
// Phase 1a (cmem-sdk rename): `runtime.runtime` is the canonical `'server'`
// value. `runtime-selector.selectRuntime()` continues to accept the legacy
// `'server-beta'` literal in settings.json and normalizes it to `'server'`.
if (runtime.runtime === 'server') {
const event: ServerRecordEventRequest = {
projectId: runtime.projectId,
contentSessionId: sessionId,
platformSource,
sourceType: 'hook',
eventType: 'tool_use',View on GitHub (pinned to e2d1df569a)
Solutions
- Safe to ignore on first run or one-off crashes — the registry rebuilds and pruneDeadEntries cleans stale rows.
- If records keep getting lost, ensure a single writer per registry path (one supervisor instance).
- Make persist() atomic (tmp file + renameSync) so crashes never leave truncated JSON.
- Distinguish ENOENT from real corruption in the log path so bootstrap noise does not mask corruption.
Example fix
// before
const raw = JSON.parse(readFileSync(this.registryPath, 'utf-8')) as PersistedRegistry;
// after (missing file is normal bootstrap, not corruption)
let raw: PersistedRegistry;
try {
raw = JSON.parse(readFileSync(this.registryPath, 'utf-8')) as PersistedRegistry;
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code !== 'ENOENT') {
logger.warn('SYSTEM', 'Failed to parse supervisor registry, rebuilding', { path: this.registryPath });
}
raw = { processes: {} };
} Defensive patterns
Strategy: fallback
Validate before calling
import { existsSync, readFileSync } from 'fs';
function loadRegistrySafe(path: string): PersistedRegistry | null {
if (!existsSync(path)) return null; // first run — normal
try {
const parsed: unknown = JSON.parse(readFileSync(path, 'utf-8'));
if (isPersistedRegistry(parsed)) return parsed;
} catch {
/* corrupt */
}
return null; // caller rebuilds from scratch
} Type guard
function isPersistedRegistry(value: unknown): value is PersistedRegistry {
if (typeof value !== 'object' || value === null) return false;
const processes = (value as PersistedRegistry).processes;
return processes === undefined || (
typeof processes === 'object' && processes !== null &&
Object.values(processes).every(v =>
typeof v === 'object' && v !== null && typeof (v as ManagedProcessRecord).pid === 'number')
);
} Prevention
- Treat the registry as a cache of PIDs, not source of truth — any record can vanish at any time by design.
- Keep one writer per registry path; multiple supervisors corrupt each other's state.
- Make persist() atomic (tmp + rename) to survive crashes mid-write.
- Expect ENOENT on first boot and log it at debug so real corruption stands out.
When it happens
Trigger: First run where the registry file does not exist yet (ENOENT hits this catch); a crash during persist() leaving a partial write; two supervisors sharing one registryPath clobbering each other; manual edits or schema change between versions.
Common situations: Fresh install bootstrap; upgrading claude-mem versions that changed the persisted shape; process killed during shutdown while the registry was being written; users sharing a home directory between two instances.
Related errors
- Folder does not exist: ${folderPath}
- Unknown Claude model: ${options.model}. Allowed: ${[...allow
- ${ctx.component} failed during ${ctx.phase}: ${causeMessage(
- Bun installation completed but binary not found. Please rest
- generation parse error: ${outcome.reason}
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/be0169063427dc1e.
Report an issue: GitHub.