{"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":1137,"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":1119,"sourceCodeEnd":1155,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/worker-service.ts#L1119-L1155","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"async function verifyWorkerAfterRestart(port: number, expectedVersion: string): Promise<boolean> {\n  try {\n    const res = await fetch(`http://127.0.0.1:${port}/health`);\n    const body = await res.json();\n    return res.ok && body.version === expectedVersion;\n  } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["restart","worker","handoff","spawn"],"backgroundTag":"process-restart-failure","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}