Skip to content

Tool Errors and Timeouts

Tool failures happen at several layers. Distinguish an expected application-domain result from an SDK handler failure, a serialization/transport failure, and a runtime timeout.

TOOL_HANDLER_NOT_FOUND is browser-side: the SDK could not find a handler and returns an error result to the agent. By contrast, TOOL_NOT_FOUND means Core’s ToolRegistry cannot find the requested tool, TOOL_ARGS_INVALID means the agent supplied non-object or schema-invalid arguments, and TOOL_TIMEOUT means Core waited too long for a result. See Error Codes for recovery guidance.

Return a structured error when the tool ran correctly but the requested action cannot be completed:

return {
error: {
code: "NO_ACTIVE_SELECTION",
message: "Select text before using replace_selection.",
details: {
documentId: activeDocument.id,
},
retryable: true,
},
};

This is a normal result. The SDK submits it to the agent and does not automatically call session.onError().

Use domain errors for:

  • missing resources;
  • invalid current UI state;
  • permission denial;
  • user cancellation;
  • version conflict; and
  • validation failure.

When no named, inline, or generic handler exists, the SDK constructs an error result with:

TOOL_HANDLER_NOT_FOUND

The result is submitted to the agent. It normally indicates an integration bug rather than a recoverable end-user problem.

Check:

  • the tool appears in skills.tools;
  • the name is identical in the definition and registration;
  • a resumed page restored its handlers;
  • component cleanup did not run too early; and
  • the handler registration uses the same session object.

If a selected handler throws or rejects, the SDK catches it and converts it into an error result with TOOL_HANDLER_ERROR.

session.onTool("save", async () => {
throw new Error("Database unavailable");
});

The raw exception does not crash the SDK event listener. The agent receives the normalized failure.

Use application logging inside the handler when stack traces or richer diagnostics are needed, because the serialized error result is intentionally compact.

After obtaining a result, the SDK calls the bridge’s submit_tool_result. If that request fails, the session emits an SDK error callback:

session.onError((error) => {
if (error.code === "SUBMIT_TOOL_RESULT_FAILED") {
showToolTransportFailure();
}
});

Possible causes:

  • extension/native/Core disconnect;
  • session ended while the handler was pending;
  • Core already timed out the tool request;
  • result cannot be cloned/serialized; or
  • runtime rejected the request ID.

The agent may remain unable to continue the turn. Do not blindly retry a side-effecting handler, because the side effect may already have happened even though result delivery failed.

Tool definition timeout:

timeoutMs: 60_000

When omitted, Core currently uses a 60-second default. The timeout controls how long the runtime waits for a result. It does not forcibly stop JavaScript in the page.

If a handler resolves late:

  1. the application side effect may already be complete;
  2. Core may already have failed or continued the turn;
  3. result submission can fail; and
  4. the session may emit SUBMIT_TOOL_RESULT_FAILED or a runtime session error.

Design handlers to settle before the deadline and implement application-side cancellation/cleanup.

Provider command timeout versus App Tool timeout

Section titled “Provider command timeout versus App Tool timeout”

A provider shell or terminal can stop waiting for pedelec-cli --thread-id <pedelec_thread_id> tool-call before Core’s timeoutMs deadline. That provider command timeout is an ambiguous transport failure; it does not prove that the App Tool failed. If pedelec-cli itself observes an IPC/transport failure before receiving a structured Core response, it automatically makes one immediate retry with the same request identity and payload. Core can join the still-running invocation or replay a recently completed result whose delivery was not confirmed, so this recovery does not intentionally dispatch a second invocation with different semantics. It cannot recover if the provider forcibly terminates the pedelec-cli process before the CLI can make its internal retry.

Structured Core responses are final for that CLI invocation, including TOOL_TIMEOUT and other App Tool/Core errors. Agents should consume the returned structured result or error; Host Context no longer asks them to exact-retry an incomplete pedelec-cli call.

When Core reaches the App Tool’s timeoutMs, it returns the formal TOOL_TIMEOUT structured error. A received TOOL_TIMEOUT means the original invocation has ended; it is not retried by the CLI and is not an instruction to continue the same invocation. A result whose IPC delivery was not confirmed may be replayed briefly, but a successfully delivered result is not retained as a general deduplication cache.

Return plain JSON-compatible values. Problematic values include:

return document.body; // DOM node
return () => 1; // function
return { value: 1n }; // BigInt
return cyclicObject; // cycle
return new Map(); // loses intended structure
return new File(...); // browser object, not a compact JSON result

Convert them:

return {
html: document.body.innerHTML,
entries: [...map.entries()],
size: file.size,
name: file.name,
};

Be mindful of payload size. Large results increase bridge cost and can exceed local IPC/message limits. Return an application-owned ID for large artifacts.

A tool schema improves agent output but does not make args trustworthy. Return a domain error rather than throwing for routine bad input:

function validateMoveArgs(raw: unknown) {
if (!raw || typeof raw !== "object") return null;
const value = raw as Record<string, unknown>;
if (typeof value.shapeId !== "string") return null;
if (typeof value.x !== "number" || !Number.isFinite(value.x)) return null;
if (typeof value.y !== "number" || !Number.isFinite(value.y)) return null;
return { shapeId: value.shapeId, x: value.x, y: value.y };
}
session.onTool("move_shape", (raw) => {
const args = validateMoveArgs(raw);
if (!args) {
return {
error: {
code: "INVALID_TOOL_ARGS",
message: "shapeId, x, and y are required.",
},
};
}
return scene.move(args);
});
Failure Safe to retry automatically?
Read-only tool transport failed before execution Sometimes, after connection recovery
Domain validation error No; agent/user should change input
User cancelled No unless user explicitly requests again
Handler threw before any side effect Possibly, if operation is known idempotent
Result submission failed after mutation Usually no; reconcile state first
Ambiguous Core IPC/transport failure observed by pedelec-cli before a structured response The CLI makes one immediate internal retry with the same request; Core can join or replay the invocation
Provider forcibly terminates pedelec-cli before its internal retry The CLI cannot recover; handle the incomplete command result as ambiguous
Received structured TOOL_TIMEOUT or another Core/App Tool error The CLI does not retry it; reconcile possible side effects where applicable
Stale UI lifecycle No; agent should read current state first

Include idempotency keys or expected-version fields in tools that can safely support retries.

Log enough correlation data to diagnose without exposing secrets:

console.error("tool failed", {
sessionId: ctx.sessionId,
turnId: ctx.turnId,
toolRequestId: ctx.toolRequestId,
tool: ctx.tool,
code: error.code,
});

Avoid logging full tool args when they can contain private page or user data.

For precise types and signatures, continue to the Tools API reference.