Files
claude-mem/src/shared/transcript-parser.ts
T
Rajiv Sinclair 1fac57535e fix: gracefully handle missing transcript files in worktree sessions (#1326)
When Claude Code runs in a worktree (via Agent tool with isolation: "worktree"),
the transcript path points to the worktree's project directory. After the
worktree is cleaned up, the Stop hook fires but the transcript file no longer
exists, causing extractLastMessage() to throw. This error triggers Claude to
respond, which fires another Stop hook, creating an infinite error loop.

Changed throws to warn-and-return-empty so the summarize hook exits cleanly
with exit 0 instead of cascading errors.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 19:59:47 -07:00

68 lines
2.0 KiB
TypeScript

import { readFileSync, existsSync } from 'fs';
import { logger } from '../utils/logger.js';
/**
* Extract last message of specified role from transcript JSONL file
* @param transcriptPath Path to transcript file
* @param role 'user' or 'assistant'
* @param stripSystemReminders Whether to remove <system-reminder> tags (for assistant)
*/
export function extractLastMessage(
transcriptPath: string,
role: 'user' | 'assistant',
stripSystemReminders: boolean = false
): string {
if (!transcriptPath || !existsSync(transcriptPath)) {
logger.warn('PARSER', `Transcript path missing or file does not exist: ${transcriptPath}`);
return '';
}
const content = readFileSync(transcriptPath, 'utf-8').trim();
if (!content) {
logger.warn('PARSER', `Transcript file exists but is empty: ${transcriptPath}`);
return '';
}
const lines = content.split('\n');
let foundMatchingRole = false;
for (let i = lines.length - 1; i >= 0; i--) {
const line = JSON.parse(lines[i]);
if (line.type === role) {
foundMatchingRole = true;
if (line.message?.content) {
let text = '';
const msgContent = line.message.content;
if (typeof msgContent === 'string') {
text = msgContent;
} else if (Array.isArray(msgContent)) {
text = msgContent
.filter((c: any) => c.type === 'text')
.map((c: any) => c.text)
.join('\n');
} else {
// Unknown content format - throw error
throw new Error(`Unknown message content format in transcript. Type: ${typeof msgContent}`);
}
if (stripSystemReminders) {
text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '');
text = text.replace(/\n{3,}/g, '\n\n').trim();
}
// Return text even if empty - caller decides if that's an error
return text;
}
}
}
// If we searched the whole transcript and didn't find any message of this role
if (!foundMatchingRole) {
return '';
}
return '';
}