{"record":{"id":"aa13888af9876f9c","repo":"thedotmack/claude-mem","slug":"generation-parse-error-outcome-reason","errorCode":null,"errorMessage":"generation parse error: ${outcome.reason}","messagePattern":"generation parse error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/server/generation/ProviderObservationGenerator.ts","lineNumber":262,"sourceCode":"      apiKeyId: payload.api_key_id,\n      actorId: payload.actor_id,\n      sourceAdapter: payload.source_adapter,\n      ...(this.options.workerId !== undefined ? { workerId: this.options.workerId } : {}),\n    };\n    const outcome: ProcessGeneratedResponseOutcome = fresh.sourceType === 'session_summary'\n      ? await processSessionSummaryResponse(persistInput)\n      : await processGeneratedResponse(persistInput);\n\n    if (outcome.kind === 'parse_error') {\n      await markGenerationFailed({\n        pool: this.options.pool,\n        job: fresh,\n        reason: outcome.reason,\n        classification: 'parse_error',\n        retryable: false,\n        ...(this.options.workerId !== undefined ? { workerId: this.options.workerId } : {}),\n      });\n      throw new Error(`generation parse error: ${outcome.reason}`);\n    }\n\n    logger.info('SYSTEM', 'generation completed', {\n      correlationId,\n      jobId: outcome.jobId,\n      bullmqJobId: job.id ?? null,\n      requestId: payloadRequestId,\n      observationCount: outcome.observations.length,\n      privateContentDetected: outcome.privateContentDetected,\n    });\n\n    return {\n      jobId: outcome.jobId,\n      status: 'completed',\n      observationCount: outcome.observations.length,\n    };\n  }\n","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/server/generation/ProviderObservationGenerator.ts#L244-L280","documentation":"Thrown by ProviderObservationGenerator when the generation outcome has kind 'parse_error' — the model returned content that could not be parsed into observations (processGeneratedResponse/processSessionSummaryResponse). Before throwing, it calls markGenerationFailed with classification 'parse_error' and retryable:false, so the job is permanently marked failed rather than requeued. The outcome.reason is included in the message.","triggerScenarios":"The LLM returned prose instead of the expected structured output; maxOutputTokens was too low and the JSON was truncated mid-object; the model emitted markdown fences or extra commentary around the JSON; a schema/prompt drift between the model and the parser; an unexpected provider response shape.","commonSituations":"Switching to a smaller/cheaper model that ignores output-format instructions. maxOutputTokens left at default (4096) for a large session that needs more. A provider A/B change in output formatting. A session summary path expecting a different envelope.","solutions":["Inspect outcome.reason in the message and the stored failed-job record to see the exact parse failure and any raw model output captured.","Raise maxOutputTokens for the provider so the response isn't truncated.","Retry the job manually if the failure was transient (a one-off malformed response).","If a model change caused it, revert to a model known to follow the output schema, or adjust the prompt/parser.","Check the provider's raw response (enable debug logging) to confirm whether it is truncation vs. format drift."],"exampleFix":"// before: default token cap truncates large session output\nnew ClaudeObservationProvider({ apiKey, model });\n// after: raise the cap so JSON isn't cut mid-object\nnew ClaudeObservationProvider({ apiKey, model, maxOutputTokens: 8192 });","handlingStrategy":"try-catch","validationCode":"// Before submitting the job, sanity-check token budget vs. session size.\nfunction estimateMaxTokensForSession(obsCharCount: number): number {\n  // rough: ~4 chars/token; ensure headroom for JSON envelope\n  const needed = Math.ceil(obsCharCount / 4) + 1024;\n  return Math.max(4096, Math.min(needed, 16384));\n}","typeGuard":null,"tryCatchPattern":"try {\n  await generator.process(job);\n} catch (error) {\n  if (/generation parse error/i.test((error as Error).message)) {\n    // job already marked failed (retryable:false). Inspect outcome.reason from\n    // the stored failed-job record; optionally re-enqueue after raising\n    // maxOutputTokens or switching models.\n    await inspectFailedJob(job.id);\n    return;\n  }\n  throw error;\n}","preventionTips":["Size maxOutputTokens to the session being summarized to avoid truncation mid-JSON.","Prefer models that reliably follow structured-output instructions.","Log the raw model output at debug level so parse failures are diagnosable.","Treat parse_error jobs as non-retriable by default; only re-enqueue after a config/model change."],"tags":["generation","parse","llm","queue","truncation"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}