thedotmack/claude-mem · error
Spawn-lock holder's worker port did not open within the cold
Error message
Spawn-lock holder's worker port did not open within the cold-boot wait (~15s)
What it means
Same cold-boot port wait, but this invocation lost the spawn gate (spawnLockHeld false): another gated launcher won and spawned, and this one waited on the winner's worker. The port still never opened within ~15s, so this hook also returns false. The spawn gate means exactly one spawner exists; losers just poll the shared port.
Source
Thrown at src/shared/worker-utils.ts:596
}
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
- Triage identically to the winner-side failure: run the worker manually to surface the pre-bind error.
- Check the worker port for conflicts and clear stale pid/lock files under ~/.claude-mem.
- The next hook event re-enters the full spawn path — verify it succeeds there before debugging further.
- Serialize cold boot by pre-warming the daemon at login.
Defensive patterns
Strategy: retry
Validate before calling
// loser path: don't spawn, just wait for the winner's port
const alive = await waitForWorkerPort({ attempts: 6, backoffMs: 500 });
if (!alive) return false; // next hook event re-enters the full spawn path Prevention
- Avoid opening many Claude sessions simultaneously right after reboot — serialize the first session.
- Pre-warm the daemon at login so spawn-gate winners and losers alike find a bound port.
- If this repeats, debug the winner's spawn (run the worker manually) — losers only mirror its failure.
When it happens
Trigger: Multiple hooks (start + context) race a cold boot; the spawn-gate winner's worker fails to bind (crash, slow init, port conflict) while all losers block on the same port-open wait and time out together.
Common situations: Several Claude sessions opened simultaneously after reboot; winner's spawn failing for environment reasons (missing env, corrupted DB) so every waiter fails.
Related errors
- Worker port did not open after lazy-spawn within the cold-bo
- Worker is healthy but not ready; skipping hook API call
- Worker lazy-spawned but did not become ready before hook rea
- [uninstall] Worker shutdown attempt failed:
- Graceful shutdown deadline exceeded — proceeding
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/1128abbc83f5f036.
Report an issue: GitHub.