affaan-m/ECC · error · ReplayError
tool.fixture_mismatch
tool.fixture_mismatch
Error message
fixture ${key.slice(0, 16)} was recorded for different arguments What it means
FixtureStore.get throws ReplayError('tool.fixture_mismatch') when the fixture found by the content key declares a different tool name or an args_hash that does not match hashValue(args) for the current call. The lookup key is derived from (tool, args), so reaching this check usually means the fixture was mutated or the key derivation changed. It prevents replaying a response recorded for different inputs.
Solutions
- Delete the mismatched fixture and re-record it in mode 'record' with the exact current tool name and arguments.
- Compare record.args_hash with store.key/hashValue(args) to see whether args or the hash function drifted; if hashValue changed after an upgrade, re-record the whole fixture set.
- Ensure args passed to replayer.call are structurally identical to record-time args (same keys, same values, no added optional fields) — canonical hashing is sensitive to content.
- Never hand-edit fixture files; treat the fixtures directory as generated output managed solely by record mode.
Example fix
// before: fixture edited to reuse another call's record, args_hash no longer matches
// fixtures/ab12....json copied over cd34....json
// after: re-record the fixture for the current args
const r = createReplayer(tools, { mode: 'record', store });
r.call('fetchWeather', { city: 'Paris' }); Defensive patterns
Strategy: validation
Validate before calling
const expectedKey = store.key(tool, args); if (!fs.existsSync(store.pathFor(expectedKey))) reRecord(tool, args);
Type guard
function argsMatchRecord(record, tool, args, hashValue) {
return record.tool === tool && record.args_hash === hashValue(args);
} Try / catch
try {
return replayer.call(tool, args);
} catch (e) {
if (e instanceof ReplayError && e.code === 'tool.fixture_mismatch' && e.message.includes('different arguments')) {
return reRecordAndReturn(tool, args);
}
throw e;
} Prevention
- Pass structurally identical args at record and replay time (freeze or deep-clone a shared args object)
- Never copy/rename fixture files manually — the filename is a content hash
- Re-record the whole fixture set after upgrading the canonical/hash module
- Keep args free of non-deterministic fields (Date.now, random ids) at the call boundary
When it happens
Trigger: store.get(tool, args) loads a fixture whose `record.tool !== tool` or `record.args_hash !== hashValue(args)` — e.g. someone hand-edited the fixture, replaced a fixture file's contents with another call's record, or the canonical hashing of args changed between record and replay (library version change, key-order normalization difference).
Common situations: Copy-pasting or renaming fixture files manually; upgrading the eval-harness/canonical module so hashValue output changes; reusing one fixture directory across two eval suites with colliding tool names; args containing non-deterministic serialization (e.g. object key order, Date objects) that hash differently on replay.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- candidate is bound to a different source or input
- historical evidence does not bind the candidate…
- replay.no_store
- tool.fixture_corrupt
- tool.fixture_missing
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/0343b6cabe9ed824.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/eval-harness/replay.js:77
};
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;
const maxRank = envelope.effectRank(options.maxEffectClass || 'SE2');
if (!['record', 'replay'].includes(mode)) {
throw new ReplayError('replay.bad_mode', `mode must be record or replay, got ${mode}`);View on GitHub (pinned to 8321021c54)