Session Auto-Recovery Mechanism in Claude Code: Runtime and Startup Protection
Claude Code implements a two-phase session auto-recovery mechanism that automatically restarts crashed generators with exponential back-off and limits, while also scanning for orphaned pending work on startup to ensure no messages are lost.
The thedotmack/claude-mem repository contains a robust session auto-recovery mechanism designed to protect active conversations against both transient process crashes and persistent failures. This system ensures that temporary outages in AI provider connections or unexpected generator exits do not result in lost user messages or stuck sessions.
Runtime Crash Recovery for Generator Processes
The primary defense against unexpected failures operates within src/services/worker/http/routes/SessionRoutes.ts. When a session's generator—the process communicating with Claude, Gemini, or OpenRouter—exits unexpectedly, the startGeneratorWithProvider method (starting at line 202) evaluates whether to initiate recovery.
Guarding Against Duplicate Restart Attempts
To prevent race conditions where multiple crash events might queue redundant restarts, the system maintains a crashRecoveryScheduled Set. Before scheduling any recovery, the code verifies that the session ID is not already present in this set, ensuring only one restart attempt is queued per session.
Consecutive Restart Limiting
Each session tracks its consecutiveRestarts counter. If this value exceeds 3, the system halts further recovery attempts, logs a CRITICAL error, and aborts the session's AbortController. This hard limit prevents infinite restart loops and protects against runaway API costs during persistent failures.
Exponential Back-off Strategy
Recovery attempts employ exponential back-off to allow external services time to stabilize. The delay calculation uses Math.min(1000 * 2 ** (session.consecutiveRestarts - 1), 8000), producing delays of 1 second, 2 seconds, 4 seconds, and capping at 8 seconds.
AbortController Cleanup
Before creating a new generator promise, the system aborts the existing AbortController and creates a fresh one. This prevents child-process leaks and ensures that zombie connections from the crashed generator cannot interfere with the new recovery attempt.
// Runtime crash recovery inside SessionRoutes.ts
if (!wasAborted) {
const pendingCount = pendingStore.getPendingCount(sessionDbId);
if (pendingCount > 0) {
// Guard duplicate restarts
if (this.crashRecoveryScheduled.has(sessionDbId)) return;
session.consecutiveRestarts = (session.consecutiveRestarts ?? 0) + 1;
// Limit restarts
if (session.consecutiveRestarts > MAX_CONSECUTIVE_RESTARTS) {
logger.error('SESSION', 'CRITICAL: Generator restart limit exceeded');
session.abortController.abort();
return;
}
// Exponential back-off
const backoffMs = Math.min(1000 * 2 ** (session.consecutiveRestarts - 1), 8000);
this.crashRecoveryScheduled.add(sessionDbId);
setTimeout(() => {
this.crashRecoveryScheduled.delete(sessionDbId);
const stillExists = this.sessionManager.getSession(sessionDbId);
if (stillExists && !stillExists.generatorPromise) {
this.startGeneratorWithProvider(stillExists, this.getSelectedProvider(), 'crash-recovery');
}
}, backoffMs);
} else {
// Clean abort when no pending work
session.abortController.abort();
session.consecutiveRestarts = 0;
}
}
Startup Recovery for Orphaned Sessions
When the worker process launches—whether after a full crash, system reboot, or deployment—the session auto-recovery mechanism scans for orphaned work. The WorkerService.startup() method triggers startPendingSessionProcessors (lines 63-81 in src/services/worker-service.ts) to identify and resume sessions with unprocessed messages.
Scanning the Pending Message Store
The recovery routine queries PendingMessageStore.getSessionsWithPendingMessages() to retrieve all session IDs with entries in the SQLite pending_messages table. This ensures that no messages persisted to disk before a crash are lost.
Throttled Session Initialization
For each orphaned session without an active generator, the system calls this.sessionManager.initializeSession(sessionDbId) followed by this.startSessionProcessor(session, 'startup-recovery'). To prevent resource exhaustion when many sessions require simultaneous recovery, the loop respects a configurable sessionLimit (default 50) and inserts a 100ms pause between each start.
// Startup recovery inside WorkerService
const orphanedSessionIds = pendingStore.getSessionsWithPendingMessages();
for (const sessionDbId of orphanedSessionIds) {
if (result.sessionsStarted >= sessionLimit) break;
// Skip if generator already running
if (this.sessionManager.getSession(sessionDbId)?.generatorPromise) {
result.sessionsSkipped++;
continue;
}
const session = this.sessionManager.initializeSession(sessionDbId);
this.startSessionProcessor(session, 'startup-recovery');
result.sessionsStarted++;
await new Promise(r => setTimeout(r, 100)); // Throttle
}
Key Implementation Files
The session auto-recovery mechanism spans several critical files in the thedotmack/claude-mem repository:
| File | Responsibility |
|---|---|
src/services/worker/http/routes/SessionRoutes.ts |
Implements per-session generator lifecycle, crash-recovery guard, restart limit, back-off, and abort handling. |
src/services/worker-service.ts |
Scans the pending-message store on worker startup and launches startup-recovery processors for orphaned sessions. |
src/services/sqlite/PendingMessageStore.ts |
Provides getSessionsWithPendingMessages() used by the startup-recovery loop to identify work requiring resumption. |
src/services/worker/SDKAgent.ts |
Emits logs when generators abort or exit unexpectedly, feeding observability for the recovery mechanisms. |
Summary
- Runtime protection: The session auto-recovery mechanism in
SessionRoutes.tsautomatically restarts crashed generators with exponential back-off (1s → 8s cap) and hard-limits consecutive restarts to 3 to prevent infinite loops. - Duplicate prevention: A
crashRecoveryScheduledSet ensures only one recovery attempt is queued per session, whileAbortControllercleanup prevents child-process leaks. - Startup resilience:
WorkerServicescansPendingMessageStoreon launch to resume orphaned sessions with unprocessed messages, respecting a default limit of 50 sessions and throttling starts with 100ms delays. - Safety boundaries: Persistent failures trigger CRITICAL logging and explicit
AbortController.abort()to halt runaway API calls.
Frequently Asked Questions
What triggers the session auto-recovery mechanism in Claude Code?
The mechanism activates in two scenarios: first, when a running generator process exits unexpectedly while the session still has pending work (runtime crash recovery), and second, when the worker service starts up and discovers sessions with unprocessed messages in the SQLite pending_messages table (startup recovery).
How does Claude Code prevent infinite restart loops during crash recovery?
The system enforces a hard limit of 3 consecutive restarts per session. Each session tracks its consecutiveRestarts counter, and when this exceeds the maximum, the system logs a CRITICAL error and aborts the session's AbortController to permanently halt further recovery attempts and prevent runaway API costs.
What is the maximum delay between crash recovery attempts?
The session auto-recovery mechanism uses exponential back-off calculated as Math.min(1000 * 2 ** (consecutiveRestarts - 1), 8000), producing delays of 1 second, 2 seconds, and 4 seconds, with a hard cap at 8 seconds to balance service recovery time against user responsiveness.
Where is the startup recovery logic implemented in the codebase?
The startup recovery routine resides in src/services/worker-service.ts within the startPendingSessionProcessors method (lines 63-81). This function queries PendingMessageStore.getSessionsWithPendingMessages() and initializes processors for orphaned sessions, applying a default session limit of 50 and 100ms throttling between starts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →