{"record":{"id":"a5ea1ce0f16473ce","repo":"thedotmack/claude-mem","slug":"generator-paused-for-abortcategory-preserving","errorCode":null,"errorMessage":"Generator paused for ${abortCategory}; preserving buffered work","messagePattern":"Generator paused for (.+?); preserving buffered work","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/worker/session/GeneratorExitHandler.ts","lineNumber":46,"sourceCode":"export async function handleGeneratorExit(\n  session: ActiveSession,\n  reason: ActiveSession['abortReason'],\n  deps: GeneratorExitDependencies\n): Promise<void> {\n  const { sessionManager, completionHandler } = deps;\n  const sessionDbId = session.sessionDbId;\n\n  const tracked = getSdkProcessForSession(sessionDbId);\n  if (tracked && !tracked.process.killed && tracked.process.exitCode === null) {\n    await ensureSdkProcessExit(tracked, 5000);\n  }\n\n  session.generatorPromise = null;\n  session.currentProvider = null;\n\n  const abortCategory = (reason ?? '').split(':')[0];\n  if (abortCategory === 'quota' || abortCategory === 'auth') {\n    logger.warn('SESSION', `Generator paused for ${abortCategory}; preserving buffered work`, {\n      sessionId: sessionDbId,\n      pendingCount: sessionManager.getMessageBuffer().getPendingCount(sessionDbId),\n    });\n    return;\n  }\n\n  logger.info('SESSION', 'Generator exited — finalizing session', { sessionId: sessionDbId, reason });\n\n  try {\n    await completionHandler.finalizeSession(sessionDbId);\n  } catch (e) {\n    const normalized = e instanceof Error ? e : new Error(String(e));\n    logger.error('SESSION', 'Finalization failed; forcing in-memory session removal', {\n      sessionId: sessionDbId,\n      reason\n    }, normalized);\n  } finally {\n    sessionManager.removeSessionImmediate(sessionDbId);","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/worker/session/GeneratorExitHandler.ts#L28-L64","documentation":"When a session's SDK generator loop exits, GeneratorExitHandler strips the abort reason to its category (the prefix before ':'). For 'quota' or 'auth' categories it deliberately skips completionHandler.finalizeSession and returns early, preserving the session's buffered messages (pendingCount is logged) so no in-flight work is lost. All other reasons fall through to normal finalization.","triggerScenarios":"session.generatorPromise settles with a reason string like \"quota: ...\" (provider quota exhausted) or \"auth: ...\" (credentials rejected/expired); the handler awaits SDK process exit (5s), clears generatorPromise/currentProvider, then takes the early return before finalizeSession.","commonSituations":"Hitting the Anthropic API usage limit mid-session; OAuth token or API key expiring/rotating while a generator was running; provider-side deauth of long-lived worker sessions.","solutions":["Restore provider capacity: wait out the quota window or re-login to refresh credentials, then start a new session — buffered work is preserved, not lost.","Check the full abort reason in worker logs (category is only the prefix before ':') to confirm whether quota or auth fired.","If quota pauses recur, reduce observation/summarization frequency in claude-mem settings to lower API burn.","Verify pendingCount in the log entry to confirm messages survived the pause; they flush when the session resumes or finalizes."],"exampleFix":"// before\nawait completionHandler.finalizeSession(sessionDbId); // runs even on quota/auth aborts, discarding buffer\n\n// after\nconst abortCategory = (reason ?? '').split(':')[0];\nif (abortCategory === 'quota' || abortCategory === 'auth') {\n  logger.warn('SESSION', `Generator paused for ${abortCategory}; preserving buffered work`, {\n    sessionId: sessionDbId,\n    pendingCount: sessionManager.getMessageBuffer().getPendingCount(sessionDbId),\n  });\n  return;\n}\nawait completionHandler.finalizeSession(sessionDbId);","handlingStrategy":"fallback","validationCode":"const abortCategory = (reason ?? '').split(':')[0];\nif (abortCategory === 'quota' || abortCategory === 'auth') {\n  // preserve buffer now, schedule resume when capacity returns\n  sessionManager.getMessageBuffer().retain(sessionDbId);\n  return;\n}","typeGuard":"const isPreservingAbortCategory = (reason?: string | null): boolean =>\n  ['quota', 'auth'].includes((reason ?? '').split(':')[0]);","tryCatchPattern":"try {\n  await runGenerator(session);\n} catch (e) {\n  const reason = e instanceof Error ? e.message : String(e);\n  if (isPreservingAbortCategory(reason)) return preserveBuffer(session);\n  throw e;\n}","preventionTips":["Monitor provider quota headroom and alert before exhaustion instead of at abort time.","Refresh OAuth/API credentials on a schedule before expiry rather than after deauth.","Treat reason strings with 'quota:'/'auth:' prefixes as resumable in your own session-management code."],"tags":["generator","quota","auth","session-lifecycle","claude-mem"],"backgroundTag":"quota-exceeded","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}