affaan-m/ECC · error · ReplayError

tool.fixture_missing

tool.fixture_missing

Error message

no fixture for ${tool} (${key.slice(0, 16)})

What it means

FixtureStore.get throws ReplayError('tool.fixture_missing') when no fixture file exists on disk for the content-addressed hash of (tool, args). In replay mode the replayer never calls the live implementation, so the stored fixture is the only source for the response; missing fixtures fail closed rather than silently returning something. This guards replay determinism: a test must never invent a response that was never recorded.

Solutions

  1. Run the same eval once in mode 'record' with a FixtureStore pointing at the same directory so the (tool, args) fixture is written, then switch back to replay.
  2. Check that the FixtureStore directory passed to new FixtureStore(dir) is the one containing the recorded fixtures (compare with where record mode wrote them).
  3. Verify the tool name and args are byte-identical to the recorded call — key ordering/extra fields change the canonical hash; print store.key(tool, args) and compare with the fixture filename.
  4. If fixtures are intentionally absent, guard the call: check store.has(tool, args) or catch ReplayError with code 'tool.fixture_missing' and record on demand.

Example fix

// before: replay fails, no fixture recorded yet
const r = createReplayer(tools, { mode: 'replay', store });
r.call('fetchWeather', { city: 'Paris' });

// after: record first, then replay
const mode = store.has('fetchWeather', { city: 'Paris' }) ? 'replay' : 'record';
const r = createReplayer(tools, { mode, store });
r.call('fetchWeather', { city: 'Paris' });
Defensive patterns

Strategy: fallback

Validate before calling

if (!store.has(toolName, args)) {
  // record first, or skip, before calling in replay mode
  const rec = createReplayer(tools, { mode: 'record', store });
  rec.call(toolName, args);
}

Type guard

function hasFixture(store, tool, args) {
  return typeof store.has === 'function' && store.has(tool, args);
}

Try / catch

try {
  return replayer.call(tool, args);
} catch (e) {
  if (e instanceof ReplayError && e.code === 'tool.fixture_missing') {
    return recordAndReturn(tool, args); // fall back to record mode
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling replayer.call(name, args) in mode 'replay' when store.get(name, args) cannot find `${key}.json` in the fixture directory — i.e. the exact (tool, args) pair was never recorded via mode 'record', the store points at the wrong directory, or the fixture file was deleted.

Common situations: Running a replay-mode eval before ever running record mode; renaming a tool or changing argument shape so the hash changes; pointing options.store at a fresh/empty directory in CI; a teammate's fixtures are gitignored so they never arrive; cleaning the fixtures dir to 'reset' tests.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

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

  put(tool, args, response) {
    const key = this.key(tool, args);
    const record = {
      key,
      tool,
      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;
  }
}

/**

View on GitHub (pinned to 8321021c54)