{"record":{"id":"64322dd5d4dc1da6","repo":"thedotmack/claude-mem","slug":"graceful-shutdown-deadline-exceeded-proceeding","errorCode":null,"errorMessage":"Graceful shutdown deadline exceeded — proceeding","messagePattern":"Graceful shutdown deadline exceeded — proceeding","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/worker-shutdown.ts","lineNumber":115,"sourceCode":"    const outcome = await Promise.race([\n      options.performGracefulShutdown().then(\n        () => 'graceful' as const,\n        (error: unknown) => {\n          // A failed graceful shutdown must not abort the restart handoff;\n          // proceed exactly like the deadline path.\n          logger.error(\n            'SYSTEM',\n            'Graceful shutdown failed — proceeding',\n            { reason: options.reason },\n            error instanceof Error ? error : new Error(String(error))\n          );\n          return 'graceful-error' as const;\n        }\n      ),\n      deadline,\n    ]);\n    if (outcome === 'deadline') {\n      logger.warn('SYSTEM', 'Graceful shutdown deadline exceeded — proceeding', {\n        deadlineMs: options.gracefulDeadlineMs,\n        reason: options.reason,\n      });\n    }\n  } finally {\n    if (deadlineTimer !== undefined) clearTimeout(deadlineTimer);\n  }\n\n  // Successor handoff — ONLY for restart; 'stop' and signal shutdowns stay\n  // kill-only. The old worker spawns its replacement as its final act, after\n  // its port is confirmed free, so the successor never races the corpse for\n  // the port. CLI `claude-mem restart` is the caller. Hook version-mismatch\n  // recycles (ensureWorkerRunning in src/shared/worker-utils.ts) never reach\n  // this: they SIGKILL the stale worker and lazy-spawn the resolved version\n  // themselves, because this handoff runs the DYING install's resolver — a\n  // stale install would respawn its own version forever (#3378). This runs\n  // inside flushResponseThen's flushed action, so it completes before that\n  // helper's process.exit(0).","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/worker-shutdown.ts#L97-L133","documentation":"Graceful shutdown (closing the HTTP server, flushing state) races a deadline timer via Promise.race. When the deadline (gracefulDeadlineMs) expires first, this warning fires and shutdown proceeds to the next phase anyway — the process still exits, just without confirming graceful cleanup finished.","triggerScenarios":"Keep-alive HTTP connections refusing to close; a telemetry or memory flush blocked on network; an in-flight SDK query whose abort is delayed; filesystem stall on the DB directory.","commonSituations":"Long-running hook clients holding sockets open; proxied or containerized environments where connection close semantics differ; slow disks stretching the flush step past the deadline.","solutions":["Usually safe to ignore — shutdown continues and the process exits; verify the next `start` is clean","If state (e.g. last observations) matters, find the hanging resource: `lsof -p <workerPid>` shows open sockets/files during shutdown","Raise gracefulDeadlineMs in settings if your flushes legitimately need longer"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make shutdown-phase work idempotent (flushes, checkpoints) so proceeding past the deadline is safe","Close long-lived client connections to the worker before triggering shutdown","Size gracefulDeadlineMs to your worst realistic flush, not the default"],"tags":["shutdown","timeout","worker","deadline"],"backgroundTag":"shutdown-timeout","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}