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

  1. Delete the mismatched fixture and re-record it in mode 'record' with the exact current tool name and arguments.
  2. 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.
  3. 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.
  4. 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

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


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)