thedotmack/claude-mem · warning

Transcript file exists but is empty: ${transcriptPath}

Error message

Transcript file exists but is empty: ${transcriptPath}

What it means

The transcript file exists but reads as empty after trim; extractLastMessage warns and returns ''. This is typically a timing artifact — the JSONL was created but no lines had been flushed when the hook read it — or an externally truncated file.

Source

Thrown at src/shared/transcript-parser.ts:17

import { readFileSync, existsSync } from 'fs';
import { logger } from '../utils/logger.js';
import { SYSTEM_REMINDER_REGEX } from '../utils/tag-stripping.js';

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 '';
  }

  return extractLastMessageFromJsonl(content, role, stripSystemReminders);
}

/**
 * Extract last message from a JSONL transcript.
 *
 * Supports two field conventions for the per-line role marker:
 * - Claude Code:  `{"type":"assistant",...}`
 * - Cursor:       `{"role":"assistant",...}`
 *
 * The most recent assistant turn is often a pure tool_use block with no text
 * content (especially in Cursor, where the agent's last action before the
 * user replies is a tool call). We therefore keep scanning backwards until
 * we find a turn with non-empty text content, instead of returning early on
 * the first matching role.

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Usually transient — the next hook event re-reads a now-populated transcript.
  2. If persistent, open the JSONL directly and confirm Claude Code is writing lines to it.
  3. Check disk space: writers can create empty files when the volume is full.
Defensive patterns

Strategy: validation

Validate before calling

const content = readFileSync(transcriptPath, 'utf-8').trim();
if (!content) {
  // transcript not flushed yet — retry or skip this event
  return '';
}

Type guard

const hasContent = (s: string): boolean => s.trim().length > 0;

Prevention

When it happens

Trigger: A hook fires before Claude Code flushes the first JSONL line to the new transcript file, or the file was truncated by disk-full/crash between creation and read.

Common situations: Very early hook timing on the first prompt of a session; disk-full leaving zero-byte files; a crashed writer process.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/cffbfc037571172e. Report an issue: GitHub.