thedotmack/claude-mem · warning
Self-replacing worker handoff did not verify in time —…
Error message
Self-replacing worker handoff did not verify in time — falling back to CLI spawn
What it means
This warning is emitted by the worker restart flow when a self-replacing handoff (the dying worker launching a successor that takes over the port) fails to be verified within the allotted polling window. The service logs the last observed handoff state and abandons verification, falling back to spawning a worker via the CLI. It is a fallback path, not a fatal failure.
Solutions
- Check the worker logs for the successor's boot errors around the restart time
- Increase the handoff verification timeout if workers on this machine boot slowly
- Verify nothing else holds the port (lsof/netstat) so the successor can bind
- Ensure the CLI spawn fallback succeeded (it will start a replacement worker anyway)
Example fix
// before: verify with default window const handoff = await waitForHandoff(oldPid, port); // after: allow more time on slow platforms const handoff = await waitForHandoff(oldPid, port, getPlatformTimeout(HOOK_TIMEOUTS.HANDOFF_VERIFY) * 2);
Defensive patterns
Strategy: fallback
Validate before calling
// Before relying on the handoff, probe the port owner
const owner = await fetch(`http://localhost:${port}/health`).then(r => r.ok).catch(() => false);
if (!owner) console.warn('handoff target not yet healthy; fallback spawn expected'); Try / catch
// Treat handoff as best-effort; always verify a worker exists afterwards const status = await ensureWorkerStarted(port); if (status === 'dead') await spawnFallback();
Prevention
- Give workers enough time to boot before declaring handoff failed
- Monitor the handoff lastObserved field to spot slow starts
- Keep the CLI spawn fallback path tested and functional
When it happens
Trigger: The dying worker's restart handoff was initiated, but waitForHandoff verification never observed a healthy successor (handoff.lastObserved stale, lastPollSawHealth false) before the verification timeout elapsed, so the code proceeds to the CLI spawn fallback instead of process.exit(0).
Common situations: Slow machine where the new worker takes longer than the verification window to bind the port and serve /health; the successor crashed on boot; port blocked by an unrelated process; platform timers shortened on Windows.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
Related errors
- Cloud sync: worker has not picked up the sync settings yet…
- Graceful shutdown deadline exceeded — proceeding
- Live PID detected but worker did not become ready before…
- Port still bound entering restart fallback — verifying…
- Request timed out after
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/cf0b0afa3f9b2276.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/worker-service.ts:1230
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 d8bc9755e7)