thedotmack/claude-mem · warning
Self-replacing worker handoff did not verify in time — falli
Error message
Self-replacing worker handoff did not verify in time — falling back to CLI spawn
What it means
On `claude-mem restart`, the dying worker spawns its own successor and the CLI verifies the handoff (new pid, matching version) within ~30s platform-adjusted time. If verification never observes a healthy successor, this warning fires and the CLI falls back to spawning a replacement itself through the spawn gate, so exactly one restart path wins.
Source
Thrown at src/services/worker-service.ts:1137
let handoffDetail = '';
let handoffSawLiveWorker = false;
if (oldPid !== null && shutdownAccepted) {
// PRIMARY: the dying worker self-replaces. Do NOT waitForPortFree and
// do NOT spawn — the successor re-binds the port within ~200ms of it
// freeing, so a port-free wait here loses the race against the very
// handoff this CLI just triggered (and a CLI spawn would be a second
// restart initiator — the disease this flow cures). Just verify the
// successor.
const handoff = await verifyRestartedWorker(port, oldPid, packageVersion, getPlatformTimeout(30000));
if (handoff.ok) {
console.log(`Worker restart verified (pid: ${handoff.pid}, version: ${handoff.version})`);
logger.info('SYSTEM', 'Worker restart verified', { pid: handoff.pid, version: handoff.version });
process.exit(0);
}
handoffDetail = `; handoff attempt: ${handoff.lastObserved}`;
handoffSawLiveWorker = handoff.lastPollSawHealth;
logger.warn('SYSTEM', 'Self-replacing worker handoff did not verify in time — falling back to CLI spawn', {
oldPid,
lastObserved: handoff.lastObserved,
});
}
// FALLBACK — reached when no worker was running, the shutdown POST was
// not accepted (e.g. the old worker predates the self-replacement
// handoff), or the handoff never produced a verified successor (its
// spawn failed). Only here may the CLI spawn, and only through the
// spawn gate so it can never race a hook's lazy-spawn.
//
// When the handoff verification's most recent poll already saw a live
// health responder, a worker (just not a verifiable successor) holds
// the port — waiting for it to free would burn the full timeout for
// nothing, so skip straight to verifying the current owner.
const restartFreed = handoffSawLiveWorker
? false
: await waitForPortFree(port, getPlatformTimeout(15000));View on GitHub (pinned to e2d1df569a)
Solutions
- Check worker logs for why the successor died (dependency preflight, port bind, DB migration failures)
- Run `claude-mem start` (or another `restart`) and watch for 'Worker started successfully'
- If it recurs, capture `lastObserved` from the warning — it states what verification actually saw (no pid, wrong version, no health)
Defensive patterns
Strategy: fallback
Validate before calling
async function verifyWorkerAfterRestart(port: number, expectedVersion: string): Promise<boolean> {
try {
const res = await fetch(`http://127.0.0.1:${port}/health`);
const body = await res.json();
return res.ok && body.version === expectedVersion;
} catch { return false; }
} Prevention
- Do not run multiple restart initiators at once — let the handoff or the CLI fallback win, not both
- Keep claude-mem CLI and worker versions in sync so version verification passes
- After any restart, confirm health + version before assuming success
When it happens
Trigger: The successor process spawned by the dying worker crashed at startup; the machine is too slow for the successor to become healthy inside the verification window; the old worker predates the self-replacement handoff feature so no successor was ever spawned.
Common situations: Restarting right after a claude-mem upgrade where the new version's worker fails an init step; restarting during heavy load; port contention from another service delaying successor startup.
Related errors
- Port still bound entering restart fallback — verifying curre
- Worker failed to start before hook, handler will proceed gra
- Worker unavailable on Windows — skipping spawn (recent attem
- Worker spawned but readiness endpoint not responding within
- Worker port did not open after lazy-spawn within the cold-bo
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/cf0b0afa3f9b2276.
Report an issue: GitHub.