{"record":{"id":"cf0b0afa3f9b2276","repo":"thedotmack/claude-mem","slug":"self-replacing-worker-handoff-did-not-verify-in-ti","errorCode":null,"errorMessage":"Self-replacing worker handoff did not verify in time — falling back to CLI spawn","messagePattern":"Self-replacing worker handoff did not verify in time — falling back to CLI spawn","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/worker-service.ts","lineNumber":1230,"sourceCode":"\n      let handoffDetail = '';\n      let handoffSawLiveWorker = false;\n      if (oldPid !== null && shutdownAccepted) {\n        // PRIMARY: the dying worker self-replaces. Do NOT waitForPortFree and\n        // do NOT spawn — the successor re-binds the port within ~200ms of it\n        // freeing, so a port-free wait here loses the race against the very\n        // handoff this CLI just triggered (and a CLI spawn would be a second\n        // restart initiator — the disease this flow cures). Just verify the\n        // successor.\n        const handoff = await verifyRestartedWorker(port, oldPid, packageVersion, getPlatformTimeout(30000));\n        if (handoff.ok) {\n          console.log(`Worker restart verified (pid: ${handoff.pid}, version: ${handoff.version})`);\n          logger.info('SYSTEM', 'Worker restart verified', { pid: handoff.pid, version: handoff.version });\n          process.exit(0);\n        }\n        handoffDetail = `; handoff attempt: ${handoff.lastObserved}`;\n        handoffSawLiveWorker = handoff.lastPollSawHealth;\n        logger.warn('SYSTEM', 'Self-replacing worker handoff did not verify in time — falling back to CLI spawn', {\n          oldPid,\n          lastObserved: handoff.lastObserved,\n        });\n      }\n\n      // FALLBACK — reached when no worker was running, the shutdown POST was\n      // not accepted (e.g. the old worker predates the self-replacement\n      // handoff), or the handoff never produced a verified successor (its\n      // spawn failed). Only here may the CLI spawn, and only through the\n      // spawn gate so it can never race a hook's lazy-spawn.\n      //\n      // When the handoff verification's most recent poll already saw a live\n      // health responder, a worker (just not a verifiable successor) holds\n      // the port — waiting for it to free would burn the full timeout for\n      // nothing, so skip straight to verifying the current owner.\n      const restartFreed = handoffSawLiveWorker\n        ? false\n        : await waitForPortFree(port, getPlatformTimeout(15000));","sourceCodeStart":1212,"sourceCodeEnd":1248,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/worker-service.ts#L1212-L1248","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)"],"exampleFix":"// before: verify with default window\nconst handoff = await waitForHandoff(oldPid, port);\n// after: allow more time on slow platforms\nconst handoff = await waitForHandoff(oldPid, port, getPlatformTimeout(HOOK_TIMEOUTS.HANDOFF_VERIFY) * 2);","handlingStrategy":"fallback","validationCode":"// Before relying on the handoff, probe the port owner\nconst owner = await fetch(`http://localhost:${port}/health`).then(r => r.ok).catch(() => false);\nif (!owner) console.warn('handoff target not yet healthy; fallback spawn expected');","typeGuard":null,"tryCatchPattern":"// Treat handoff as best-effort; always verify a worker exists afterwards\nconst status = await ensureWorkerStarted(port);\nif (status === 'dead') await spawnFallback();","preventionTips":["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"],"tags":["worker","restart","fallback","timeout"],"backgroundTag":"request-timeout","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}