thedotmack/claude-mem · error
Worker port did not open after lazy-spawn within the cold-bo
Error message
Worker port did not open after lazy-spawn within the cold-boot wait (~15s)
What it means
This invocation spawned the worker (spawnLockHeld true), then waitForWorkerPort — 6 attempts with 500ms backoff (~15.5s, matching POST_SPAWN_WAIT) — never saw the port open, so the hook gives up. The long budget exists because cold macOS+Chroma boots need ~7s (#2795); exceeding ~15s means the worker is stuck or crashing before it can bind.
Source
Thrown at src/shared/worker-utils.ts:595
});
}
return false;
}
} else {
logger.info('SYSTEM', 'Another launcher holds the spawn lock — skipping lazy-spawn and waiting for its worker');
}
// Cold boot (#2795): on the first session after a reboot the SessionStart
// `start` hook is booting the daemon in parallel, and a cold macOS+Chroma
// worker needs ~7s to bind. The old 3-attempt/250ms budget (~0.75s) expired
// long before that, so the context (and session-init) hooks raced boot and
// soft-failed to empty — dropping memory injection and the user_prompts row
// (the upstream trigger for #2794). Wait up to ~15.5s (≈ POST_SPAWN_WAIT) so
// whichever worker wins the port is seen before we give up.
const alive = await waitForWorkerPort({ attempts: 6, backoffMs: 500 });
if (!alive) {
logger.warn('SYSTEM', spawnLockHeld
? 'Worker port did not open after lazy-spawn within the cold-boot wait (~15s)'
: 'Spawn-lock holder\'s worker port did not open within the cold-boot wait (~15s)');
return false;
}
} finally {
if (spawnLockHeld) releaseSpawnLock();
}
const ready = await waitForWorkerReadiness();
if (!ready) {
logger.warn('SYSTEM', 'Worker lazy-spawned but did not become ready before hook readiness timeout');
return false;
}
// Amplifier guard: even if the worker that won the port is still stale,
// never recycle a second time in the same hook invocation.
if (expectedPluginVersion !== null) {
await warnIfVersionStillMismatched(expectedPluginVersion);
}
return true;
}View on GitHub (pinned to e2d1df569a)
Solutions
- Run the worker manually (bun plugin/scripts/worker-service.cjs) and read the pre-bind crash output.
- Check the port is free: lsof -i :$CLAUDE_MEM_WORKER_PORT; kill whatever squats it.
- If logs show Chroma contention, clear stale Chroma lock files under ~/.claude-mem/chroma and retry.
- Pre-start the daemon at login so the first session does not cold-boot inside a hook.
Defensive patterns
Strategy: retry
Validate before calling
const alive = await waitForWorkerPort({ attempts: 6, backoffMs: 500 });
if (!alive) {
// diagnose pre-bind crash: run worker manually, check port
return false;
} Type guard
const isPortOpen = async (port: number): Promise<boolean> => {
try { await fetch(`http://127.0.0.1:${port}/health`); return true; }
catch { return false; }
}; Prevention
- Pre-start the worker daemon at login so hooks never cold-boot under a 15s ceiling.
- Keep the worker port dedicated — check lsof for squatters when the warning appears.
- On very slow disks, warm Chroma (first-run model download) before the first session.
When it happens
Trigger: The spawned worker crashes before listen (bad config, DB lock, missing env), a cold start drags past 15s, or another process squats the configured worker port so bind fails.
Common situations: First boot after reboot on slow disks; first-run Chroma initialization; port conflict from an orphaned or foreign process on CLAUDE_MEM_WORKER_PORT.
Related errors
- Worker is healthy but not ready; skipping hook API call
- Spawn-lock holder's worker port did not open within the cold
- Worker lazy-spawned but did not become ready before hook rea
- Worker spawned but readiness endpoint not responding within
- [uninstall] Worker shutdown attempt failed:
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/7228998580479f27.
Report an issue: GitHub.