affaan-m/ECC · error · ReplayError

tool.fixture_corrupt

tool.fixture_corrupt

Error message

fixture ${key.slice(0, 16)} is not valid JSON

What it means

FixtureStore.get throws ReplayError('tool.fixture_corrupt') when the fixture file exists but its contents cannot be parsed as JSON. This is a fail-closed integrity check: replay must refuse a fixture whose payload cannot be trusted instead of returning garbage. The short key prefix in the message identifies which fixture file to inspect.

Solutions

  1. Delete the corrupt fixture file `${key}.json` from the store directory and re-run the eval in mode 'record' to regenerate it from the live implementation.
  2. Inspect the file with a JSON validator (e.g. `node -e "JSON.parse(require('fs').readFileSync('<file>','utf8'))"`) to see the exact parse error before deleting, in case it is trivially fixable.
  3. Check git status / merge conflict markers in the fixtures directory — a conflicted or partially staged fixture is a common cause.
  4. Regenerate all fixtures if several are corrupt: wipe the store directory and run the full record pass once.

Example fix

// before: hand-edited fixture with trailing comma
{"response": {"temp": 21,}}

// after: regenerate via record mode (or fix to valid JSON)
const r = createReplayer(tools, { mode: 'record', store });
r.call('fetchWeather', recordedArgs);
Defensive patterns

Strategy: try-catch

Validate before calling

const raw = fs.readFileSync(fixturePath, 'utf8');
try { JSON.parse(raw); } catch (e) { regenerateFixture(tool, args); }

Type guard

function isParsableJson(text) {
  try { JSON.parse(text); return true; } catch { return false; }
}

Try / catch

try {
  return replayer.call(tool, args);
} catch (e) {
  if (e instanceof ReplayError && e.code === 'tool.fixture_corrupt') {
    fs.unlinkSync(store.pathFor(e.key));
    return recordAndReturn(tool, args);
  }
  throw e;
}

Prevention

When it happens

Trigger: store.get(tool, args) reads `${key}.json` and JSON.parse throws — e.g. the file was truncated mid-write, hand-edited with a syntax error, saved with a BOM or invalid encoding, or overwritten by a non-JSON process.

Common situations: Manually editing fixture files to tweak recorded responses; a crash (Ctrl+C, OOM) during store.put's writeFileSync leaving a partial file; merge conflicts inside a fixture file resolved incorrectly; transferring fixtures between machines with encoding conversion.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/191891f8831c3606. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/eval-harness/replay.js:74

      args_hash: hashValue(args),
      response_hash: hashValue(response),
      response,
    };
    fs.writeFileSync(this.pathFor(key), canonicalJson(record) + '\n', 'utf8');
    return record;
  }

  get(tool, args) {
    const key = this.key(tool, args);
    const filePath = this.pathFor(key);
    if (!fs.existsSync(filePath)) {
      throw new ReplayError('tool.fixture_missing', `no fixture for ${tool} (${key.slice(0, 16)})`, { tool, key });
    }
    let record;
    try {
      record = JSON.parse(fs.readFileSync(filePath, 'utf8'));
    } catch (_error) {
      throw new ReplayError('tool.fixture_corrupt', `fixture ${key.slice(0, 16)} is not valid JSON`, { tool, key });
    }
    if (record.tool !== tool || record.args_hash !== hashValue(args)) {
      throw new ReplayError('tool.fixture_mismatch', `fixture ${key.slice(0, 16)} was recorded for different arguments`, { tool, key });
    }
    if (record.response_hash !== hashValue(record.response)) {
      throw new ReplayError('tool.fixture_mismatch', `fixture ${key.slice(0, 16)} response hash does not match its content`, { tool, key });
    }
    return record;
  }
}

/**
 * tools: { name: { effect_class, determinism: 'deterministic'|'nondeterministic', impl(args) } }
 * options: { mode: 'record'|'replay', store: FixtureStore, maxEffectClass: 'SE2', onCall(entry) }
 */
function createReplayer(tools, options = {}) {
  const mode = options.mode || 'replay';
  const store = options.store;

View on GitHub (pinned to 8321021c54)