can1357/oh-my-pi · error · Error
invokeTool: delegation depth exceeded 8 (recursive invokeToo
Error message
invokeTool: delegation depth exceeded 8 (recursive invokeTool for "${name}"?) What it means
invokeTool tracks delegation depth and hard-caps it at 8 to stop recursive tool invocation (a tool whose execution invokes itself, directly or via a chain of extensions/tools). Exceeding the cap throws this Error naming the tool.
Source
Thrown at packages/coding-agent/src/extensibility/extensions/runner.ts:587
params: Record<string, unknown>,
options?: {
signal?: AbortSignal;
onUpdate?: AgentToolUpdateCallback<TDetails>;
depth?: number;
/**
* The caller tool's own context. Reused for the native call so metadata the native tool
* reads — `toolCall` (write/edit LSP batch flushing) and provider metadata /
* `providerSafetyApproved` (computer) — is preserved. Falls back to a fresh session tool
* context only when the caller had none.
*/
callerContext?: AgentToolContext;
},
): Promise<AgentToolResult<TDetails>> {
const resolved = this.#nativeToolResolver?.(name);
if (!resolved) throw new Error(`invokeTool: no native built-in named "${name}" to delegate to`);
const depth = options?.depth ?? 0;
if (depth >= 8) {
throw new Error(`invokeTool: delegation depth exceeded 8 (recursive invokeTool for "${name}"?)`);
}
const toolCallId = `invoke-${name}-${Date.now().toString(36)}-${depth}`;
return (await resolved.tool.execute(
toolCallId,
params as never,
options?.signal,
options?.onUpdate as never,
options?.callerContext ?? resolved.makeContext(),
)) as AgentToolResult<TDetails>;
}
constructor(
private readonly extensions: Extension[],
private readonly runtime: ExtensionRuntime,
/** Ignored: `cwd` is always read live via the `cwd` getter below, not cached here. */
_initialCwd: string,
private readonly sessionManager: SessionManager,
private readonly modelRegistry: ModelRegistry,View on GitHub (pinned to 9690622007)
Solutions
- Break the recursion: in your handler, invoke the underlying tool directly (bypass invokeTool) or skip interception when options.depth > 0.
- Pass/propagate depth correctly when re-invoking so the counter increments, and add an explicit depth guard in your handler.
- Restructure so the intercepted path terminates (invoke the resolved built-in once, not through the dispatcher).
Example fix
// before
api.onToolCall('bash', async evt => {
return runner.invokeTool('bash', evt.params); // re-enters dispatcher forever
});
// after
api.onToolCall('bash', async evt => {
if ((evt.depth ?? 0) > 0) return; // let inner call pass through
return runner.invokeTool('bash', evt.params, { depth: 1 });
}); Defensive patterns
Strategy: try-catch
Validate before calling
if ((options?.depth ?? 0) >= 8) {
throw new Error('refusing to delegate: depth limit would be exceeded');
}
await runner.invokeTool(name, params, { depth: (options?.depth ?? 0) + 1 }); Type guard
function canDelegate(depth: number | undefined): boolean {
return (depth ?? 0) < 8;
} Try / catch
try {
return await runner.invokeTool(name, params, { depth });
} catch (err) {
if (err.message.includes('delegation depth exceeded 8')) {
logger.error('recursive invokeTool detected', { name, depth });
throw new Error(`circular tool delegation involving '${name}'`);
}
throw err;
} Prevention
- In tool-call interceptors, never re-invoke the same tool through the dispatcher; call the underlying resolved tool directly.
- Thread options.depth through every delegation hop so the counter is accurate.
- Add a guard clause: skip interception when depth > 0.
- Model tool chains as a DAG and test for cycles.
When it happens
Trigger: An extension's invokeTool handler re-invokes the same tool, or a cycle of tools (A invokes B invokes A) nests more than 8 deep because options.depth is propagated.
Common situations: Extension wrapper intercepts a tool and calls invokeTool for the same tool name without a base-case condition; mutually recursive tool wrappers; default depth not threaded through a custom delegation chain.
Related errors
- invokeTool: no native built-in named "${name}" to delegate t
- Pending action store unavailable for custom tools in this ru
- Extension runtime not initialized. Action methods cannot be
- Composer shape id must be a non-empty trimmed string
- Composer shape "${id}" must have a label
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/272e09b10cf892e5.
Report an issue: GitHub.