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

  1. Safe to ignore on first run or one-off crashes — the registry rebuilds and pruneDeadEntries cleans stale rows.
  2. If records keep getting lost, ensure a single writer per registry path (one supervisor instance).
  3. Make persist() atomic (tmp file + renameSync) so crashes never leave truncated JSON.
  4. 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

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


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/be0169063427dc1e. Report an issue: GitHub.