affaan-m/ECC · error · ReplayError
tool.effect_forbidden
tool.effect_forbidden
Error message
tool ${name} is ${tool.effect_class}, above the allowed ${options.maxEffectClass || 'SE2'} What it means
replayer.call throws ReplayError('tool.effect_forbidden') when the tool's effect rank exceeds options.maxEffectClass (default 'SE2'), refusing high-effect tools such as SE3+ (network, money-touching, counterparty-facing operations). A second, unconditional variant fires in replay mode for any tool ranked SE3 or above — such tools can never be replayed from fixtures regardless of maxEffectClass. The call is also recorded with status 'refused'.
Solutions
- For record mode with legitimate high-effect tools, raise the ceiling: createReplayer(tools, { mode: 'record', store, maxEffectClass: 'SE3' }).
- For replay mode, do not attempt to replay SE3+ tools — split them out of the replayed suite or replace them with a lower-effect, deterministic stub tool (e.g. SE1 fake) declared in the tools map.
- If the tool was declared with too high an effect class by mistake, correct its effect_class to the accurate level.
- Inspect r.calls for entries with status 'refused' and code 'tool.effect_forbidden' to find all offending calls before re-running.
Example fix
// before: replaying a money tool — forbidden
const tools = { chargeCard: { effect_class: 'SE3', determinism: 'nondeterministic', impl } };
r.call('chargeCard', { amount: 10 }); // throws even with fixture
// after: use a deterministic SE1 stub for replay
const tools = {
chargeCard: { effect_class: 'SE3', determinism: 'nondeterministic', impl },
chargeCardStub: { effect_class: 'SE1', determinism: 'deterministic', impl: () => ({ ok: true }) },
};
r.call('chargeCardStub', { amount: 10 }); Defensive patterns
Strategy: validation
Validate before calling
const { effectRank } = require('./envelope');
const maxRank = effectRank(options.maxEffectClass || 'SE2');
for (const name of plannedToolCalls) {
const t = tools[name];
if (effectRank(t.effect_class) > maxRank || (mode === 'replay' && effectRank(t.effect_class) >= effectRank('SE3'))) {
throw new Error(`${name} (${t.effect_class}) cannot be called in this suite configuration`);
}
} Type guard
function callableInSuite(tool, maxEffectClass, mode, envelope) {
const rank = envelope.effectRank(tool.effect_class);
return rank <= envelope.effectRank(maxEffectClass || 'SE2') &&
!(mode === 'replay' && rank >= envelope.effectRank('SE3'));
} Try / catch
try {
return replayer.call(name, args);
} catch (e) {
if (e instanceof ReplayError && e.code === 'tool.effect_forbidden') {
console.warn(`Refused high-effect tool ${name}; use a lower-effect stub or record mode`);
return stubResponse(name, args);
}
throw e;
} Prevention
- Never attempt to replay SE3+ (network/payment) tools — replace them with deterministic low-effect stubs
- Raise maxEffectClass explicitly for record-mode suites that legitimately exercise high-effect tools
- Check replayer.calls for status:'refused' entries after each run to catch silent refusals
- Review effect_class declarations when adding money-touching or network tools — the fence is intentional
When it happens
Trigger: Calling a tool declared SE3/SE4 with default options (maxEffectClass 'SE2'); in replay mode, calling any tool with rank >= SE3 even if maxEffectClass was raised; passing a maxEffectClass string whose rank is lower than the tool's (e.g. 'SE1').
Common situations: Trying to replay a live-payment or network tool in tests — the library intentionally forbids this; forgetting to raise maxEffectClass for a record-mode suite that legitimately exercises SE3 tools; new team members attempting to 'fix' fixture gaps by replaying expensive tools; running the same suite config in replay that was written for record.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- artifact must be a resident regular file
- artifact path escapes output directory
- artifact path must be canonical and absolute
- artifact permits provider execution
- capsule.secret_canary
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/bef1608d61f104bb.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/eval-harness/replay.js:128
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 });
return response;
},
};
}
View on GitHub (pinned to 8321021c54)