{"record":{"id":"a87af164eb99b48b","repo":"affaan-m/ECC","slug":"tool-fixture-missing","errorCode":"tool.fixture_missing","errorMessage":"no fixture for ${tool} (${key.slice(0, 16)})","messagePattern":"no fixture for (.+?) \\((.+?)\\)","errorType":"error_code","errorClass":"ReplayError","httpStatus":null,"severity":"error","filePath":"scripts/lib/eval-harness/replay.js","lineNumber":68,"sourceCode":"\n  put(tool, args, response) {\n    const key = this.key(tool, args);\n    const record = {\n      key,\n      tool,\n      args_hash: hashValue(args),\n      response_hash: hashValue(response),\n      response,\n    };\n    fs.writeFileSync(this.pathFor(key), canonicalJson(record) + '\\n', 'utf8');\n    return record;\n  }\n\n  get(tool, args) {\n    const key = this.key(tool, args);\n    const filePath = this.pathFor(key);\n    if (!fs.existsSync(filePath)) {\n      throw new ReplayError('tool.fixture_missing', `no fixture for ${tool} (${key.slice(0, 16)})`, { tool, key });\n    }\n    let record;\n    try {\n      record = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n    } catch (_error) {\n      throw new ReplayError('tool.fixture_corrupt', `fixture ${key.slice(0, 16)} is not valid JSON`, { tool, key });\n    }\n    if (record.tool !== tool || record.args_hash !== hashValue(args)) {\n      throw new ReplayError('tool.fixture_mismatch', `fixture ${key.slice(0, 16)} was recorded for different arguments`, { tool, key });\n    }\n    if (record.response_hash !== hashValue(record.response)) {\n      throw new ReplayError('tool.fixture_mismatch', `fixture ${key.slice(0, 16)} response hash does not match its content`, { tool, key });\n    }\n    return record;\n  }\n}\n\n/**","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/eval-harness/replay.js#L50-L86","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Check that the FixtureStore directory passed to new FixtureStore(dir) is the one containing the recorded fixtures (compare with where record mode wrote them).","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.","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."],"exampleFix":"// before: replay fails, no fixture recorded yet\nconst r = createReplayer(tools, { mode: 'replay', store });\nr.call('fetchWeather', { city: 'Paris' });\n\n// after: record first, then replay\nconst mode = store.has('fetchWeather', { city: 'Paris' }) ? 'replay' : 'record';\nconst r = createReplayer(tools, { mode, store });\nr.call('fetchWeather', { city: 'Paris' });","handlingStrategy":"fallback","validationCode":"if (!store.has(toolName, args)) {\n  // record first, or skip, before calling in replay mode\n  const rec = createReplayer(tools, { mode: 'record', store });\n  rec.call(toolName, args);\n}","typeGuard":"function hasFixture(store, tool, args) {\n  return typeof store.has === 'function' && store.has(tool, args);\n}","tryCatchPattern":"try {\n  return replayer.call(tool, args);\n} catch (e) {\n  if (e instanceof ReplayError && e.code === 'tool.fixture_missing') {\n    return recordAndReturn(tool, args); // fall back to record mode\n  }\n  throw e;\n}","preventionTips":["Commit the fixtures directory to the repo so CI and teammates have the same fixture set","Always run a record pass after changing a tool's arguments schema","Use store.has(tool, args) before switching a suite to replay mode","Keep a single shared constant for the fixture directory used by both record and replay runs"],"tags":["fixture","replay","file-not-found","testing"],"backgroundTag":"file-not-found","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}