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

  1. Verify the transcript file exists and is readable at the logged transcriptPath
  2. Re-open the JSONL transcript and confirm the last assistant entry is well-formed
  3. Update claude-mem to a version matching your Claude Code transcript format
  4. 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

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


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)