ruvnet/ruflo · error

KV cache integrity check failed: hash mismatch

Error message

KV cache integrity check failed: hash mismatch

What it means

The RVKV format ends with SHA-256 over bytes 44..offset (all entry data); loadKvCache() recomputes it and compares to the stored footer. A mismatch means the file is structurally complete but its content hash differs — silent corruption after write (bit rot, torn write from a concurrent writer) or tampering. This is the integrity check that catches damage errors 120/121 miss.

Solutions

  1. Delete the cache file and let it regenerate — do not attempt repair
  2. Ensure exactly one process/engine instance writes a given kvCachePath
  3. Persist atomically (temp file + rename) to eliminate torn writes
  4. If mismatches persist across fresh regenerations, suspect the storage medium or the transfer channel
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await engine.loadKvCache(kvCachePath);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('KV cache integrity check failed')) {
    await rm(kvCachePath, { force: true }); // content hash mismatch — drop and rebuild
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: `engine.loadKvCache(path)` on a structurally valid file where `stored.equals(computed)` is false — e.g. one flipped byte in the entry region, two engine instances writing the same kvCachePath concurrently (interleaved writes), or a transfer channel that mangles bytes (FTP ASCII mode).

Common situations: Multiple processes persisting KV caches to one path; failing storage media; manual binary edits; caches copied through a byte-mangling transport.

Related errors


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

Appendix: source

Thrown at v3/@claude-flow/cli/src/appliance/gguf-engine.ts:416

    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`);
  }

  /** Return metadata for all loaded models. */
  getLoadedModels(): GgufMetadata[] { return Array.from(this.loadedModels.values()); }

  /** Store a key-value pair in the in-memory KV cache. */
  setKvEntry(key: string, value: Buffer): void { this.kvCache.set(key, value); }

  /** Retrieve a key-value pair from the in-memory KV cache. */
  getKvEntry(key: string): Buffer | undefined { return this.kvCache.get(key); }

  /** Release resources, unload models, and optionally persist the KV cache. */
  async shutdown(): Promise<void> {
    if (this.config.kvCachePath && this.kvCache.size > 0) {
      try { await this.persistKvCache(this.config.kvCachePath); }

View on GitHub (pinned to fa13ee4ad6)