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

  1. Raise maxToolCalls in the execution options to a value that fits the task's expected number of tool calls
  2. Inspect the script/agent for loops that re-invoke tools without progress and fix the loop logic
  3. Break the task into multiple executions, each with its own tool-call budget
  4. 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

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


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/cfdfad548cfe1c06. Report an issue: GitHub.