From b079003cf6fea09d4829fec72bf9f17c2b344889 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 6 Mar 2026 14:36:18 +0100 Subject: [PATCH] docs(coding-agent): clarify that tool errors must be thrown, not returned Returning { isError: true } from a tool's execute function was silently ignored - the agent loop only sets isError via the catch block. Fix the with-deps example to throw instead, add a clear note in the Tool Definition docs section, and update the Error Handling summary. closes #1881 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/docs/extensions.md | 14 +++++++++++++- .../examples/extensions/with-deps/index.ts | 6 +----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 2f905b60..33e60d09 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixed - Fixed `/new` leaving startup header content, including the changelog, visible after starting a fresh session ([#1880](https://github.com/badlogic/pi-mono/issues/1880)) +- Fixed misleading docs and example implying that returning `{ isError: true }` from a tool's `execute` function marks the execution as failed; errors must be signaled by throwing ([#1881](https://github.com/badlogic/pi-mono/issues/1881)) ## [0.56.2] - 2026-03-05 diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md index 7f625703..66a555f2 100644 --- a/packages/coding-agent/docs/extensions.md +++ b/packages/coding-agent/docs/extensions.md @@ -1340,6 +1340,18 @@ pi.registerTool({ }); ``` +**Signaling errors:** To mark a tool execution as failed (sets `isError: true` on the result and reports it to the LLM), throw an error from `execute`. Returning a value never sets the error flag regardless of what properties you include in the return object. + +```typescript +// Correct: throw to signal an error +async execute(toolCallId, params) { + if (!isValid(params.input)) { + throw new Error(`Invalid input: ${params.input}`); + } + return { content: [{ type: "text", text: "OK" }], details: {} }; +} +``` + **Important:** Use `StringEnum` from `@mariozechner/pi-ai` for string enums. `Type.Union`/`Type.Literal` doesn't work with Google's API. ### Overriding Built-in Tools @@ -1880,7 +1892,7 @@ const highlighted = highlightCode(code, lang, theme); - Extension errors are logged, agent continues - `tool_call` errors block the tool (fail-safe) -- Tool `execute` errors are reported to the LLM with `isError: true` +- Tool `execute` errors must be signaled by throwing; the thrown error is caught, reported to the LLM with `isError: true`, and execution continues ## Mode Behavior diff --git a/packages/coding-agent/examples/extensions/with-deps/index.ts b/packages/coding-agent/examples/extensions/with-deps/index.ts index ee53a46b..a352f695 100644 --- a/packages/coding-agent/examples/extensions/with-deps/index.ts +++ b/packages/coding-agent/examples/extensions/with-deps/index.ts @@ -21,11 +21,7 @@ export default function (pi: ExtensionAPI) { execute: async (_toolCallId, params) => { const result = ms(params.duration as ms.StringValue); if (result === undefined) { - return { - content: [{ type: "text", text: `Invalid duration: "${params.duration}"` }], - isError: true, - details: {}, - }; + throw new Error(`Invalid duration: "${params.duration}"`); } return { content: [{ type: "text", text: `${params.duration} = ${result} milliseconds` }],