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
- Verify logPath targets a file this RvfEventLog version wrote (hex-dump the first bytes and compare against a known-good log)
- If corrupted, restore the log from a intact backup, or rebuild from snapshots when they exist
- 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
- Verify the magic bytes of the target file before pointing logPath at it
- Back up logs only while the writer is stopped
- Keep event formats versioned; never point a new RvfEventLog at a foreign event file
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
- Event must have a valid aggregateId string
- Event must have a valid type string
- RvfEventLog not initialized. Call initialize() first.
- Too many events to replay (${events.length}). Consider creat
- Concurrent write detected on aggregate '${aggregateId}'. Res
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ae0652db945ffa0f.
Report an issue: GitHub.