thedotmack/claude-mem · warning
Stop hook: failed to extract last assistant message for…
Error message
Stop hook: failed to extract last assistant message for session ${sessionId}: ${err instanceof Error ? err.message : err} What it means
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.
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
Example fix
// before
const turn = extractLastAssistantTurn(transcriptPath, true);
// after
if (!existsSync(transcriptPath)) {
logger.warn('HOOK', 'transcript missing, skipping');
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
const turn = extractLastAssistantTurn(transcriptPath, true); Defensive patterns
Strategy: fallback
Validate before calling
if (!transcriptPath || !existsSync(transcriptPath)) {
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
} Type guard
function hasReadableTranscript(p?: string): p is string {
return typeof p === 'string' && p.length > 0 && existsSync(p) && statSync(p).isFile();
} Try / catch
try {
const turn = extractLastAssistantTurn(transcriptPath, true);
} catch (err) {
logger.warn('HOOK', `failed to extract: ${err instanceof Error ? err.message : err}`);
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- Adapter rejected input
- Could not persist SessionEnd request for worker recovery
- Failed to parse files JSON in hasDirectChildFile
- Failed to parse files_modified JSON
- Failed to parse files_read JSON
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/b99c3051ac01b79a.
Report an issue: GitHub.
Appendix: source
Thrown at src/cli/handlers/summarize.ts:103
// the user's IDE session is running, read from its transcript.
let observedModel: string | undefined;
if (input.lastAssistantMessage !== undefined) {
lastAssistantMessage = stripMemoryTags(input.lastAssistantMessage);
observedModel = transcriptPath ? extractLastAssistantModel(transcriptPath) : undefined;
} else {
if (!transcriptPath) {
logger.debug('HOOK', `No transcriptPath in Stop hook input for session ${sessionId} - skipping summary`);
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
try {
// One read of the transcript yields both the text and the model.
const turn = extractLastAssistantTurn(transcriptPath, true);
lastAssistantMessage = stripMemoryTags(turn.text);
observedModel = turn.model;
} catch (err) {
logger.warn('HOOK', `Stop hook: failed to extract last assistant message for session ${sessionId}: ${err instanceof Error ? err.message : err}`);
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
}
if (!lastAssistantMessage || !lastAssistantMessage.trim()) {
logger.debug('HOOK', 'No assistant message available - skipping summary', {
sessionId,
transcriptPath
});
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
logger.dataIn('HOOK', 'Stop: Requesting summary', {
hasLastAssistantMessage: !!lastAssistantMessage
});
const platformSource = normalizePlatformSource(input.platform);
View on GitHub (pinned to d8bc9755e7)