affaan-m/ECC · error · ReplayError
tool.unknown
tool.unknown
Error message
tool ${name} is not declared What it means
replayer.call(name, args) throws ReplayError('tool.unknown') when the requested tool name is not a key of the tools map passed to createReplayer. The replayer only permits calls to explicitly declared tools so every call's effect class and determinism are known. There is no dynamic registration after construction.
Solutions
- Add the tool to the tools map passed to createReplayer with full declaration (effect_class, determinism, impl), or
- Fix the name in the call() site to exactly match a declared key — print Object.keys(tools) to compare.
- Synchronize tool names across config, fixtures, and call sites; consider exporting a shared TOOLS constant used by both declaration and call sites.
- If the name comes from data (test plan/fixture), validate it against the registry before running the suite.
Example fix
// before
r.call('fetchWether', { city: 'Paris' }); // typo
// after
r.call('fetchWeather', { city: 'Paris' }); Defensive patterns
Strategy: type-guard
Validate before calling
if (!(toolName in tools)) {
throw new Error(`unknown tool ${toolName}; available: ${Object.keys(tools).join(', ')}`);
} Type guard
function isDeclaredTool(tools, name) {
return Object.prototype.hasOwnProperty.call(tools, name);
} Try / catch
try {
return replayer.call(name, args);
} catch (e) {
if (e instanceof ReplayError && e.code === 'tool.unknown') {
throw new Error(`${name} not declared; declared tools: ${Object.keys(tools).join(', ')}`);
}
throw e;
} Prevention
- Keep tool names in one shared constant/module used by both the registry and call sites
- Validate external/test-plan tool names against the registry before execution
- Grep for call sites after renaming any tool
- Keep one naming convention (camelCase) for all tool keys
When it happens
Trigger: Calling r.call('fetchWether', ...) with a typo; calling a tool that was declared in another suite's tools map; a name mismatch between where tools are declared and where call() happens (e.g. kebab-case vs camelCase); calling a tool that was removed in a refactor.
Common situations: Renaming a tool in the registry without updating call sites; loading tool names from config/test data where the fixture references an old name; sharing fixture sets between suites with different registries; case-sensitive lookup ('FetchWeather' ≠ 'fetchWeather').
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- replay.bad_mode
- -32602
- a claim token is required
- a confirmed nonempty coordinate is required
- a generated candidate cannot claim original-source identity
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/b8af2532dc31669d.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/eval-harness/replay.js:123
throw new ReplayError('replay.bad_declaration', `tool ${name} must declare determinism`);
}
}
const calls = [];
const emit = (entry) => {
calls.push(entry);
if (typeof options.onCall === 'function') {
options.onCall(entry);
}
};
return {
mode,
calls,
call(name, args = {}) {
const tool = tools[name];
if (!tool) {
throw new ReplayError('tool.unknown', `tool ${name} is not declared`);
}
const rank = envelope.effectRank(tool.effect_class);
if (rank > maxRank) {
emit({ tool: name, mode, status: 'refused', code: 'tool.effect_forbidden' });
throw new ReplayError('tool.effect_forbidden', `tool ${name} is ${tool.effect_class}, above the allowed ${options.maxEffectClass || 'SE2'}`, { tool: name });
}
if (mode === 'replay') {
if (rank >= envelope.effectRank('SE3')) {
emit({ tool: name, mode, status: 'refused', code: 'tool.effect_forbidden' });
throw new ReplayError('tool.effect_forbidden', `tool ${name} (${tool.effect_class}) can never be replayed`, { tool: name });
}
const record = store.get(name, args);
emit({ tool: name, mode, status: 'replayed', fixture_key: record.key, args_hash: record.args_hash, response_hash: record.response_hash });
return record.response;
}
const response = tool.impl(args);
const record = store.put(name, args, response);
emit({ tool: name, mode, status: 'recorded', fixture_key: record.key, args_hash: record.args_hash, response_hash: record.response_hash });View on GitHub (pinned to 8321021c54)