diff --git a/src/agents/pi-embedded-runner/run/payloads.test.ts b/src/agents/pi-embedded-runner/run/payloads.test.ts index 7a38cc8d2..d0083285c 100644 --- a/src/agents/pi-embedded-runner/run/payloads.test.ts +++ b/src/agents/pi-embedded-runner/run/payloads.test.ts @@ -171,10 +171,41 @@ describe("buildEmbeddedRunPayloads", () => { toolResultFormat: "plain", }); - expect(payloads).toHaveLength(1); - expect(payloads[0]?.isError).toBe(true); - expect(payloads[0]?.text).toContain("Exec"); - expect(payloads[0]?.text).toContain("code 1"); + // Exec exit code errors are now suppressed as internal/recoverable errors + expect(payloads).toHaveLength(0); + }); + + it("suppresses exec exit code errors (grep no matches, etc.)", () => { + const payloads = buildEmbeddedRunPayloads({ + assistantTexts: [], + toolMetas: [], + lastAssistant: undefined, + lastToolError: { toolName: "exec", error: "Command exited with code 1" }, + sessionKey: "session:telegram", + inlineToolResultsAllowed: false, + verboseLevel: "off", + reasoningLevel: "off", + toolResultFormat: "plain", + }); + + // Exec/bash exit code errors should not be sent to the user + expect(payloads).toHaveLength(0); + }); + + it("suppresses bash exit code errors", () => { + const payloads = buildEmbeddedRunPayloads({ + assistantTexts: [], + toolMetas: [], + lastAssistant: undefined, + lastToolError: { toolName: "bash", error: "Command exited with code 127" }, + sessionKey: "session:telegram", + inlineToolResultsAllowed: false, + verboseLevel: "off", + reasoningLevel: "off", + toolResultFormat: "plain", + }); + + expect(payloads).toHaveLength(0); }); it("suppresses recoverable tool errors containing 'required'", () => { diff --git a/src/agents/pi-embedded-runner/run/payloads.ts b/src/agents/pi-embedded-runner/run/payloads.ts index 14d304e41..b014314e9 100644 --- a/src/agents/pi-embedded-runner/run/payloads.ts +++ b/src/agents/pi-embedded-runner/run/payloads.ts @@ -182,7 +182,13 @@ export function buildEmbeddedRunPayloads(params: { // Check if this is a recoverable/internal tool error that shouldn't be shown to users // when there's already a user-facing reply (the model should have retried). const errorLower = (params.lastToolError.error ?? "").toLowerCase(); + const toolNameLower = (params.lastToolError.toolName ?? "").toLowerCase(); + const isExecTool = toolNameLower === "exec" || toolNameLower === "bash"; + // Exec/bash non-zero exit codes are internal errors (e.g., grep no matches) that + // the model should handle, not surface to users. + const isExecExitCodeError = isExecTool && errorLower.includes("exited with code"); const isRecoverableError = + isExecExitCodeError || errorLower.includes("required") || errorLower.includes("missing") || errorLower.includes("invalid") ||