ruvnet/ruflo · error

KV cache file missing SHA256 footer

Error message

KV cache file missing SHA256 footer

What it means

After loadKvCache() consumes all entryCount entries it requires 32 more bytes of mandatory SHA-256 footer (`offset + 32 > data.length` fails). This error means the entries parsed cleanly but the file ends with fewer than 32 bytes remaining — i.e. the integrity footer is absent. Like error 120 it indicates a file cut off mid-write or truncated after the entry region.

Solutions

  1. Delete the cache file and let the engine regenerate it from live state
  2. If restoring from a backup or copy, re-copy and verify the byte size matches the original
  3. Check whether any tool (log rotation, sync daemon) truncates or rewrites the cache path
  4. Switch persistence to an atomic write (temp file + rename) so a footer-less file can never be observed
Defensive patterns

Strategy: try-catch

Validate before calling

import { stat, rm } from 'node:fs/promises';
// A complete RVKV file is at least 44B header + 0 entries + 32B footer = 76B
const st = await stat(kvCachePath).catch(() => null);
if (!st || st.size < 76) {
  await rm(kvCachePath, { force: true }); // cannot possibly contain header + footer
}

Try / catch

try {
  await engine.loadKvCache(kvCachePath);
} catch (err) {
  if (err instanceof Error && err.message === 'KV cache file missing SHA256 footer') {
    await rm(kvCachePath, { force: true }); // regenerate from live state
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: `engine.loadKvCache(path)` on a file truncated by exactly the footer (or more), a hand-assembled cache written without the 32-byte SHA-256 footer, or a partially synced/copied file whose tail is missing.

Common situations: Crash during persistKvCache's single writeFile call; partial file copy; a sync/rotation tool that truncated the cache path; a custom script that rewrote cache entries but forgot to append the hash footer.

Related errors


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

Appendix: source

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

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

  /** 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); }

View on GitHub (pinned to fa13ee4ad6)