thedotmack/claude-mem · warning
Spawn-lock holder's worker not ready within window
Error message
Spawn-lock holder's worker not ready within window
What it means
This launcher lost the spawn gate (another hook, the MCP server, or the CLI already holds it), skipped its own spawn, and waited on the lock holder's worker. That worker did not pass /readiness within the window, but some sign of life (health endpoint, PID file, process) remains, so the path returns 'warming' with this warning rather than declaring failure.
Source
Thrown at src/services/worker-spawner.ts:172
}
} else {
logger.info('SYSTEM', 'Another launcher holds the spawn lock — skipping duplicate spawn and waiting for its worker');
}
const ready = await waitForReadiness(port, getPlatformTimeout(HOOK_TIMEOUTS.READINESS_WAIT));
if (!ready) {
const workerStillHealthy = await waitForHealth(port, 1000);
const workerPidStillAlive = cleanStalePidFile() === 'alive';
const spawnedProcessStillAlive = spawnedPid !== undefined && spawnedPid > 0 && isPidAlive(spawnedPid);
if (!workerStillHealthy && !workerPidStillAlive && !spawnedProcessStillAlive) {
logger.error('SYSTEM', spawnLockHeld
? 'Worker exited before readiness endpoint became available'
: 'Spawn-lock holder never produced a live worker before readiness timed out');
return 'dead';
}
logger.warn('SYSTEM', spawnLockHeld
? 'Worker spawned but readiness endpoint not responding within window'
: 'Spawn-lock holder\'s worker not ready within window');
return 'warming';
}
clearWorkerSpawnAttempted();
// touchPidFile is existsSync-guarded and merely refreshes the live worker's
// pid-file mtime — correct for lock losers too, since the worker IS up.
touchPidFile();
logger.info('SYSTEM', spawnLockHeld
? 'Worker started successfully'
: 'Worker is up (started by another launcher)');
return 'ready';
} finally {
if (spawnLockHeld) releaseSpawnLock();
}
}
View on GitHub (pinned to e2d1df569a)
Solutions
- Nothing to fix in the loser path — poll /readiness and the worker typically turns ready shortly
- If it never turns ready, debug the winner's spawn: check worker logs for startup failures
- Avoid racing launchers by starting the worker once at session/boot time before hooks fire
Defensive patterns
Strategy: retry
Validate before calling
async function waitForHolderWorker(port: number, timeoutMs = 60000): Promise<boolean> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
try { if (await fetch(`http://127.0.0.1:${port}/readiness`).then(r => r.ok)) return true; } catch {}
await new Promise(r => setTimeout(r, 1000));
}
return false;
} Prevention
- Never race launchers — start the worker once up front so hooks and MCP only find it running
- When you lost the spawn lock, wait on the holder's worker instead of spawning a competitor
- A single warm-up wait at session start removes the whole class of lock-loser waits
When it happens
Trigger: Two hooks fire simultaneously on a machine with no worker: one wins the spawn lock, the other waits; the winner's worker is still initializing when the loser's readiness wait expires.
Common situations: Parallel tool invocations at session start; MCP server and a hook both lazy-spawning; slow init making every waiter's window expire.
Related errors
- Live PID detected but worker did not become ready before tim
- Worker is alive but readiness timed out — proceeding anyway
- Worker spawned but readiness endpoint not responding within
- Worker is healthy but not ready; skipping hook API call
- Spawn-lock holder's worker port did not open within the cold
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/16041b06bf916e9e.
Report an issue: GitHub.