different-ai/openwork · error · ToolRuntimeError
ToolCallLimitExceeded
ToolCallLimitExceeded
Error message
Execution exceeded its tool-call limit of ${maxToolCalls}. What it means
The codemode tool runtime tracks every tool call made during a scripted execution and enforces an optional per-execution cap (maxToolCalls). When recordCall sees calls.length already at the cap, it throws ToolCallLimitExceeded to stop runaway agent loops. This is an intentional guardrail against infinite tool-calling loops, not a bug.
Source
Thrown at packages/codemode/src/tool-runtime.ts:750
return onEnd({
...call,
durationMs: Date.now() - startedAt,
outcome: "failure",
message,
})
}),
)
}
const decodeOutput = (value: unknown, name: string) =>
Effect.try({
try: () => copyIn(value, `Result from tool '${name}'`),
catch: () => new ToolRuntimeError("InvalidToolOutput", `Invalid output from tool '${name}'.`),
})
const recordCall = (call: ToolCall): void => {
if (maxToolCalls !== undefined && calls.length >= maxToolCalls) {
throw new ToolRuntimeError("ToolCallLimitExceeded", `Execution exceeded its tool-call limit of ${maxToolCalls}.`)
}
calls.push(call)
}
return {
root: new ToolReference([]),
calls,
keys: (path) => namespaceKeys(callableTools, path),
invoke: (path, args) =>
Effect.gen(function* () {
const name = path.join(".")
const externalArgs = args.map((arg) => copyOut(copyIn(arg, `Arguments for tool '${name}'`)))
const call = { name }
const recordAndObserve = (input: unknown) =>
Effect.sync(() => {
recordCall(call)
return calls.length - 1
}).pipe(Effect.tap((index) => hooks?.onToolCallStart?.({ index, name, input }) ?? Effect.void))View on GitHub (pinned to 2b7df46e8a)
Solutions
- Raise maxToolCalls in the execution options to a value that fits the task's expected number of tool calls
- Inspect the script/agent for loops that re-invoke tools without progress and fix the loop logic
- Break the task into multiple executions, each with its own tool-call budget
- If the call itself was legitimate, ensure calls are deduplicated (cache results) so fewer tool calls are consumed
Example fix
// before
await runtime.execute(code, { maxToolCalls: 5 })
// after
await runtime.execute(code, { maxToolCalls: 50 }) Defensive patterns
Strategy: try-catch
Validate before calling
const remaining = maxToolCalls - callsSoFar if (remaining <= 0) throw new Error(`Tool-call budget exhausted before execution`);
Try / catch
try {
await runtime.execute(code, { maxToolCalls })
} catch (e) {
if (e instanceof ToolRuntimeError && e.code === 'ToolCallLimitExceeded') {
// resume with a higher limit or split the task
} else throw e
} Prevention
- Set maxToolCalls generously above the expected number of tool calls
- Deduplicate repeated tool invocations in scripts
- Loop over data in-process instead of issuing one tool call per item
- Monitor calls length and stop the script before the cap
When it happens
Trigger: Executing a codemode script whose interpreter issues more tool calls than maxToolCalls for the run; the call that exceeds the cap is rejected before it is dispatched to the tool.
Common situations: Agents stuck in retry loops calling the same failing tool; scripts with unbounded loops over large datasets each invoking a tool; misconfigured (too low) maxToolCalls for a legitimate multi-tool task; long-running autonomous sessions where the cap was set for a single turn.
Related errors
- Expected '${key}' to be a boolean.
- Failed to parse script as a Program node.
- String.${name} expects argument ${index + 1} to be a number.
- String.normalize expects the form "NFC", "NFD", "NFKC", or "
- String.replaceAll requires a regular expression with the glo
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/cfdfad548cfe1c06.
Report an issue: GitHub.