thedotmack/claude-mem · warning
Worker unavailable on Windows — skipping spawn (recent attem
Error message
Worker unavailable on Windows — skipping spawn (recent attempt failed within cooldown)
What it means
On Windows, after a failed spawn attempt claude-mem writes a cooldown marker file; shouldSkipSpawnOnWindows() suppresses further spawn attempts while the marker is younger than 2 minutes (WINDOWS_SPAWN_COOLDOWN_MS). This warning fires instead of spawning and the caller gets 'dead', preventing a hook-driven spawn storm on broken setups.
Source
Thrown at src/services/worker-spawner.ts:133
return ready ? 'ready' : 'warming';
}
const portInUse = await isPortInUse(port);
if (portInUse) {
logger.info('SYSTEM', 'Port in use, waiting for worker to become healthy');
const healthy = await waitForHealth(port, getPlatformTimeout(HOOK_TIMEOUTS.PORT_IN_USE_WAIT));
if (healthy) {
clearWorkerSpawnAttempted();
const ready = await waitForReadiness(port, getPlatformTimeout(HOOK_TIMEOUTS.READINESS_WAIT));
logger.info('SYSTEM', 'Worker is now healthy');
return ready ? 'ready' : 'warming';
}
logger.error('SYSTEM', 'Port in use but worker not responding to health checks');
return 'dead';
}
if (shouldSkipSpawnOnWindows()) {
logger.warn('SYSTEM', 'Worker unavailable on Windows — skipping spawn (recent attempt failed within cooldown)');
return 'dead';
}
// Spawn gate (src/shared/worker-spawn-gate.ts): only ONE gated launcher —
// hook, MCP server, or the CLI restart fallback — may spawn at a time. (The
// dying worker's restart handoff in worker-shutdown.ts is deliberately NOT
// gated: it is the primary spawner on restart, and hooks wait for its
// successor.) Losing the lock never fails this path; the loser skips its
// spawn and waits for the holder's worker. The winner holds the lock through
// the readiness wait and releases it in finally on every exit path.
const spawnLockHeld = acquireSpawnLock();
let spawnedPid: number | undefined;
try {
if (spawnLockHeld) {
logger.info('SYSTEM', 'Starting worker daemon', { workerScriptPath });
markWorkerSpawnAttempted();
spawnedPid = spawnDaemon(workerScriptPath, port);
if (spawnedPid === undefined) {View on GitHub (pinned to e2d1df569a)
Solutions
- Check the worker log for the original spawn failure and fix it (install runtime, whitelist the daemon, fix directory permissions)
- Wait at least 2 minutes for the cooldown to lapse, then retry — the marker self-heals
- If certain the root cause is fixed, delete the cooldown marker file to retry immediately
Defensive patterns
Strategy: retry
Validate before calling
const WINDOWS_SPAWN_COOLDOWN_MS = 2 * 60 * 1000;
function cooldownRemaining(markerPath: string): number {
try {
const mtime = fs.statSync(markerPath).mtimeMs;
return Math.max(0, WINDOWS_SPAWN_COOLDOWN_MS - (Date.now() - mtime));
} catch { return 0; }
} Prevention
- Fix the underlying spawn failure (AV exclusions, runtime install, permissions) — the cooldown only stops the storm
- On Windows, start the worker once at boot so hooks never spawn under cooldown
- Wait out the 2-minute window before retrying; only delete the marker after fixing the root cause
When it happens
Trigger: Any hook-triggered lazy spawn within 120 seconds of a previous spawn failure on Windows; the marker file's mtime is recent and no healthy worker ever appeared.
Common situations: Antivirus or Defender blocking the daemon executable; missing node/bun runtime; permission errors on the log or data directory; the marker left behind by an earlier unrelated failure that was since fixed.
Related errors
- Self-replacing worker handoff did not verify in time — falli
- Worker failed to start before hook, handler will proceed gra
- Worker spawned but readiness endpoint not responding within
- Worker port did not open after lazy-spawn within the cold-bo
- uv installation completed but binary not found. Please resta
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/dce40710ae11e267.
Report an issue: GitHub.