affaan-m/ECC · error · ReplayError
replay.bad_declaration
replay.bad_declaration
Error message
tool ${name} must declare an effect_class What it means
createReplayer validates every entry in the tools map and throws ReplayError('replay.bad_declaration') when a tool's effect_class is missing or not one of envelope.EFFECT_CLASSES. Effect classes (SE0..SE3) drive the effect fence that decides whether a tool may run or be replayed, so an undeclared class makes the tool unusable and the whole replayer refuses to start.
Solutions
- Add a valid effect_class from envelope.EFFECT_CLASSES to the tool declaration, e.g. { effect_class: 'SE2', determinism: 'deterministic', impl }.
- Log the declared class for the offending tool (the error message names it) and compare against the allowed list from require('./envelope').EFFECT_CLASSES.
- Fix case/typo issues — classes are exact strings like 'SE2'; 'se2' will not pass.
- After a library upgrade, re-check all tool declarations against the new EFFECT_CLASSES list and update renamed classes.
Example fix
// before
const tools = {
fetchWeather: { determinism: 'deterministic', impl: fetchWeather },
};
// after
const tools = {
fetchWeather: { effect_class: 'SE2', determinism: 'deterministic', impl: fetchWeather },
}; Defensive patterns
Strategy: validation
Validate before calling
const { EFFECT_CLASSES } = require('./envelope');
for (const [name, tool] of Object.entries(tools)) {
if (!EFFECT_CLASSES.includes(tool.effect_class)) {
throw new Error(`tool ${name} has invalid effect_class: ${tool.effect_class}`);
}
} Type guard
function declaresEffectClass(tool, EFFECT_CLASSES) {
return typeof tool === 'object' && tool !== null &&
EFFECT_CLASSES.includes(tool.effect_class);
} Try / catch
try {
return createReplayer(tools, { mode, store });
} catch (e) {
if (e instanceof ReplayError && e.code === 'replay.bad_declaration' && e.message.includes('effect_class')) {
console.error('Fix tool declarations:', e.message);
}
throw e;
} Prevention
- Create tools through a makeTool({ effect_class, determinism, impl }) factory that validates fields
- Import EFFECT_CLASSES and validate declarations in a unit test for the tools registry
- After upgrading the harness, diff EFFECT_CLASSES against declarations in use
- Use exact string constants (e.g. const SE2 = 'SE2') instead of inline literals
When it happens
Trigger: Passing a tool object without an effect_class property, with a typo ('se2', 'SE_2'), or with a custom class name not present in envelope.EFFECT_CLASSES — e.g. `{ name: { impl(args) {...}, determinism: 'deterministic' } }`.
Common situations: Adding a new tool and forgetting the effect_class field; renaming effect classes in a library upgrade so old declarations no longer match; hand-writing tool registries instead of using a shared declaration helper; copy-pasting tool defs from an older example that used a different field name.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Choose at least one guided harness: Claude, Codex, or Kimi.
- failure.reason
- genre has no references
- Invalid : unsupported hook event
- invalid plan-canvas server port
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/cf24c370ca77f7df.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/eval-harness/replay.js:102
}
/**
* 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}`);
}
if (!store) {
throw new ReplayError('replay.no_store', 'a FixtureStore is required');
}
for (const [name, tool] of Object.entries(tools)) {
if (!envelope.EFFECT_CLASSES.includes(tool.effect_class)) {
throw new ReplayError('replay.bad_declaration', `tool ${name} must declare an effect_class`);
}
if (!['deterministic', 'nondeterministic'].includes(tool.determinism)) {
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 = {}) {View on GitHub (pinned to 8321021c54)