{"record":{"id":"b99c3051ac01b79a","repo":"thedotmack/claude-mem","slug":"stop-hook-failed-to-extract-last-assistant-message-for","errorCode":null,"errorMessage":"Stop hook: failed to extract last assistant message for session ${sessionId}: ${err instanceof Error ? err.message : err}","messagePattern":"Stop hook: failed to extract last assistant message for session (.+?): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/cli/handlers/summarize.ts","lineNumber":103,"sourceCode":"    // the user's IDE session is running, read from its transcript.\n    let observedModel: string | undefined;\n\n    if (input.lastAssistantMessage !== undefined) {\n      lastAssistantMessage = stripMemoryTags(input.lastAssistantMessage);\n      observedModel = transcriptPath ? extractLastAssistantModel(transcriptPath) : undefined;\n    } else {\n      if (!transcriptPath) {\n        logger.debug('HOOK', `No transcriptPath in Stop hook input for session ${sessionId} - skipping summary`);\n        return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };\n      }\n\n      try {\n        // One read of the transcript yields both the text and the model.\n        const turn = extractLastAssistantTurn(transcriptPath, true);\n        lastAssistantMessage = stripMemoryTags(turn.text);\n        observedModel = turn.model;\n      } catch (err) {\n        logger.warn('HOOK', `Stop hook: failed to extract last assistant message for session ${sessionId}: ${err instanceof Error ? err.message : err}`);\n        return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };\n      }\n    }\n\n    if (!lastAssistantMessage || !lastAssistantMessage.trim()) {\n      logger.debug('HOOK', 'No assistant message available - skipping summary', {\n        sessionId,\n        transcriptPath\n      });\n      return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };\n    }\n\n    logger.dataIn('HOOK', 'Stop: Requesting summary', {\n      hasLastAssistantMessage: !!lastAssistantMessage\n    });\n\n    const platformSource = normalizePlatformSource(input.platform);\n","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/cli/handlers/summarize.ts#L85-L121","documentation":"The Stop hook reads the session transcript to extract the last assistant message (and model) for summarization. If extractLastAssistantTurn throws — unreadable transcript, unexpected format, or empty/corrupt file — this warning is logged with the session ID and the hook exits gracefully with SUCCESS, skipping summary generation. Summaries are best-effort; this error means one session simply will not be summarized.","triggerScenarios":"extractLastAssistantTurn throws on transcriptPath: file missing or unreadable, transcript JSONL contains a malformed last line, or the parser encounters an unexpected message shape. Then lastAssistantMessage/observedModel stay unset.","commonSituations":"Session still being flushed when the Stop hook fires; transcript deleted or rotated; newer Claude Code transcript format the parser does not recognize; permission issues in the projects directory.","solutions":["Verify the transcript file exists and is readable at the logged transcriptPath","Re-open the JSONL transcript and confirm the last assistant entry is well-formed","Update claude-mem to a version matching your Claude Code transcript format","Re-run the summarize step for that session manually; the hook already continued safely"],"exampleFix":"// before\nconst turn = extractLastAssistantTurn(transcriptPath, true);\n// after\nif (!existsSync(transcriptPath)) {\n  logger.warn('HOOK', 'transcript missing, skipping');\n  return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };\n}\nconst turn = extractLastAssistantTurn(transcriptPath, true);","handlingStrategy":"fallback","validationCode":"if (!transcriptPath || !existsSync(transcriptPath)) {\n  return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };\n}","typeGuard":"function hasReadableTranscript(p?: string): p is string {\n  return typeof p === 'string' && p.length > 0 && existsSync(p) && statSync(p).isFile();\n}","tryCatchPattern":"try {\n  const turn = extractLastAssistantTurn(transcriptPath, true);\n} catch (err) {\n  logger.warn('HOOK', `failed to extract: ${err instanceof Error ? err.message : err}`);\n  return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };\n}","preventionTips":["Always guard transcript existence before extraction","Handle partial/last-line-incomplete JSONL gracefully","Keep parser in sync with Claude Code transcript format changes","Treat summarization as best-effort — never fail the hook on it"],"tags":["hook","transcript","parsing","summarization"],"backgroundTag":"file-read-failed","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}