ruvnet/ruflo · critical

[RvfEventLog] Invalid file header in ${filePath}

Error message

[RvfEventLog] Invalid file header in ${filePath}

What it means

When replaying a log file (for events or snapshots), RvfEventLog verifies the MAGIC byte prefix at offset 0 and throws if the file is shorter than the magic or the bytes differ. This fires when logPath points at a file that is not an RVF log, an empty or truncated file, or a log corrupted by a crash mid-write. The check is deliberately early so corrupt bytes never enter the in-memory index.

Source

Thrown at v3/@claude-flow/shared/src/events/rvf-event-log.ts:341

    if (this.config.verbose) {
      console.log('[RvfEventLog] persist() called — all data already on disk');
    }
  }

  // ===========================================================================
  // Private Helpers
  // ===========================================================================

  /**
   * Replay an RVF file and invoke `handler` for every decoded record.
   * Used both for events and snapshots.
   */
  private replayFile(filePath: string, handler: (record: any) => void): void {
    const buf = readFileSync(filePath);

    // Validate magic
    if (buf.length < MAGIC_LENGTH || buf.subarray(0, MAGIC_LENGTH).compare(MAGIC) !== 0) {
      throw new Error(`[RvfEventLog] Invalid file header in ${filePath}`);
    }

    let offset = MAGIC_LENGTH;

    const MAX_PAYLOAD_SIZE = 100 * 1024 * 1024; // 100MB safety limit
    while (offset + LENGTH_PREFIX_BYTES <= buf.length) {
      const payloadLength = buf.readUInt32BE(offset);
      offset += LENGTH_PREFIX_BYTES;

      if (payloadLength > MAX_PAYLOAD_SIZE) {
        if (this.config.verbose) {
          console.warn(`[RvfEventLog] Payload size ${payloadLength} exceeds safety limit`);
        }
        break;
      }

      if (offset + payloadLength > buf.length) {
        // Truncated record — stop reading (crash recovery).

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Verify logPath targets a file this RvfEventLog version wrote (hex-dump the first bytes and compare against a known-good log)
  2. If corrupted, restore the log from a intact backup, or rebuild from snapshots when they exist
  3. Copy/backup log files only while the writer is stopped, and write via the append+rename path so torn files cannot be replayed

Example fix

// before
new RvfEventLog({ logPath: './data/events.jsonl' }); // wrong format -> throws on replay

// after
new RvfEventLog({ logPath: './data/events.rvf' }); // file written by RvfEventLog itself
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';
function looksLikeRvfFile(path: string): boolean {
  const head = readFileSync(path).subarray(0, 4); // MAGIC_LENGTH bytes
  return head.length === 4 && head.equals(KNOWN_MAGIC_BYTES);
}

Try / catch

try {
  await log.initialize(); // replays the log
} catch (e) {
  if (e instanceof Error && e.message.includes('Invalid file header')) {
    // log is wrong-format or corrupt: restore from backup or rebuild from snapshots
  } else throw e;
}

Prevention

When it happens

Trigger: Configuring logPath to a JSON-lines event log or other non-RVF file; replaying a zero-byte file left by a failed first write; a log truncated by a process crash or disk-full during appendRecord; a backup copied while the writer was active.

Common situations: Migrating from another event-store format and pointing at old logs; restoring partial backups; two event-log versions or apps sharing one path; interrupted copies via rsync/scp of a live log.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/ae0652db945ffa0f. Report an issue: GitHub.