ruvnet/ruflo · error · Error
KV cache file truncated
Error message
KV cache file truncated
What it means
Thrown by loadKvCache inside the entry loop when offset + 8 > data.length — i.e. the buffer cannot even fit the 8-byte per-entry header (key length u32 + value length u32) for the next entry. The declared entryCount says more entries remain, but the file ran out of bytes. Indicates truncation during the entry region (the second truncation check; the first is for the entry body).
Source
Thrown at v3/@claude-flow/cli/src/appliance/gguf-engine.ts:401
if (this.config.verbose) console.log(`[gguf-engine] KV cache persisted: ${this.kvCache.size} entries`);
}
/** Restore KV cache from an RVF-compatible binary file. */
async loadKvCache(inputPath: string): Promise<void> {
const data = await readFile(inputPath);
if (data.length < 44) throw new Error('KV cache file too small');
const magic = data.readUInt32LE(0);
if (magic !== RVKV_MAGIC) throw new Error(`Invalid KV cache magic: 0x${magic.toString(16)}`);
const version = data.readUInt32LE(4);
if (version !== RVKV_VERSION) throw new Error(`Unsupported KV cache version: ${version}`);
const entryCount = data.readUInt32LE(40);
let offset = 44;
const entries = new Map<string, Buffer>();
for (let i = 0; i < entryCount; i++) {
if (offset + 8 > data.length) throw new Error('KV cache file truncated');
const keyLen = data.readUInt32LE(offset);
const valLen = data.readUInt32LE(offset + 4);
offset += 8;
if (offset + keyLen + valLen > data.length) throw new Error('KV cache file truncated');
entries.set(data.toString('utf-8', offset, offset + keyLen), Buffer.from(data.subarray(offset + keyLen, offset + keyLen + valLen)));
offset += keyLen + valLen;
}
// Verify footer hash (mandatory)
if (offset + 32 > data.length) {
throw new Error('KV cache file missing SHA256 footer');
}
const stored = data.subarray(offset, offset + 32);
const computed = createHash('sha256').update(data.subarray(44, offset)).digest();
if (!stored.equals(computed)) throw new Error('KV cache integrity check failed: hash mismatch');
this.kvCache = entries;
if (this.config.verbose) console.log(`[gguf-engine] KV cache loaded: ${entries.size} entries`);View on GitHub (pinned to 6b01dc5a68)
Solutions
- Make persistence atomic: write to a temp file then rename over the target, so a crash never leaves a half-written cache.
- Delete the truncated cache and regenerate from the current in-memory state.
- Before loading, sanity-check that file size is plausible for entryCount (rough lower bound) and purge if not.
- Wrap loadKvCache so truncation triggers a cold start instead of crashing inference.
Example fix
// before
// persistKvCache writes non-atomically; a crash truncates the file
await writeFile(path, Buffer.concat([header, entryData, footer]));
// after
const tmp = `${path}.tmp`;
await writeFile(tmp, Buffer.concat([header, entryData, footer]));
await rename(tmp, path); // atomic on same filesystem Defensive patterns
Strategy: fallback
Validate before calling
// Atomic writes prevent truncated files; before loading, sanity-check size vs entryCount:
const data = await readFile(path);
if (data.length < 44) return; // error 110 path
const entryCount = data.readUInt32LE(40);
const minSize = 44 + entryCount * 8; // each entry needs at least its 8-byte header
if (data.length < minSize) {
await fsUnlink(path).catch(() => {});
return;
}
await engine.loadKvCache(path); Type guard
function plausiblyComplete(data: Buffer): boolean {
if (data.length < 44) return false;
const entryCount = data.readUInt32LE(40);
return data.length >= 44 + entryCount * 8;
} Try / catch
try {
await engine.loadKvCache(path);
} catch (e) {
if (e instanceof Error && /truncated/.test(e.message)) {
await fsUnlink(path).catch(() => {});
return;
}
throw e;
} Prevention
- Make persistKvCache atomic (write temp, then rename).
- Cold-start on truncation rather than failing inference.
- Ensure the process is not OOM/SIGKILLed mid-write (resource limits).
- Validate size vs declared entryCount before parsing.
When it happens
Trigger: A cache file whose header claims N entries but is truncated before all N entry headers are present — typically a persistKvCache that was interrupted (process killed, disk full) mid-write, since writeFile is not atomic.
Common situations: Process killed (SIGKILL/OOM) during persistKvCache; disk filled mid-write; a copy of the cache file was interrupted. Because writeFile overwrites in place non-atomically, a crash leaves a partial file with a valid header and entryCount but missing entries.
Related errors
- KV cache file too small
- Invalid KV cache magic: 0x${magic.toString(16)}
- KV cache file missing SHA256 footer
- KV cache integrity check failed: hash mismatch
- No KV cache output path specified
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/a0b9eeff23a6ff20.
Report an issue: GitHub.