From e5547294f3eaa2354b2f98d465203e715c2aaa36 Mon Sep 17 00:00:00 2001 From: Naveen Chatlapalli Date: Thu, 29 Jan 2026 01:32:23 -0600 Subject: [PATCH] fix(telegram): treat exec exit code errors as internal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command exit codes (like grep returning 1 for no matches) should not be forwarded to users. The model sees these errors in its context and can decide how to proceed - surfacing them clutters the chat with technical messages that aren't actionable by users. Add "exited with code", "exit code", and "aborted" to the list of recoverable/internal error patterns that are suppressed from user output. Before: "⚠️ 🛠️ Exec: grep ... failed: Command exited with code 1" After: (error stays internal, model continues normally) Fixes #3765 Co-Authored-By: Claude Opus 4.5 --- .../pi-embedded-runner/run/payloads.test.ts | 25 +++++++++++++++---- src/agents/pi-embedded-runner/run/payloads.ts | 7 +++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/agents/pi-embedded-runner/run/payloads.test.ts b/src/agents/pi-embedded-runner/run/payloads.test.ts index 7a38cc8d2..735f69319 100644 --- a/src/agents/pi-embedded-runner/run/payloads.test.ts +++ b/src/agents/pi-embedded-runner/run/payloads.test.ts @@ -148,7 +148,7 @@ describe("buildEmbeddedRunPayloads", () => { expect(payloads[0]?.text).toBe("All good"); }); - it("adds tool error fallback when the assistant only invoked tools", () => { + it("suppresses exit code errors as internal (model sees them in context)", () => { const payloads = buildEmbeddedRunPayloads({ assistantTexts: [], toolMetas: [], @@ -171,10 +171,9 @@ 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"); + // Exit code errors should be treated as internal - the model sees them in context + // and can decide how to proceed. Users shouldn't see "grep returned exit code 1" etc. + expect(payloads).toHaveLength(0); }); it("suppresses recoverable tool errors containing 'required'", () => { @@ -226,6 +225,22 @@ describe("buildEmbeddedRunPayloads", () => { expect(payloads).toHaveLength(0); }); + it("suppresses aborted command errors", () => { + const payloads = buildEmbeddedRunPayloads({ + assistantTexts: [], + toolMetas: [], + lastAssistant: undefined, + lastToolError: { toolName: "exec", error: "Command aborted by signal SIGTERM" }, + sessionKey: "session:telegram", + inlineToolResultsAllowed: false, + verboseLevel: "off", + reasoningLevel: "off", + toolResultFormat: "plain", + }); + + expect(payloads).toHaveLength(0); + }); + it("shows non-recoverable tool errors to the user", () => { const payloads = buildEmbeddedRunPayloads({ assistantTexts: [], diff --git a/src/agents/pi-embedded-runner/run/payloads.ts b/src/agents/pi-embedded-runner/run/payloads.ts index 567314f9f..857ed43eb 100644 --- a/src/agents/pi-embedded-runner/run/payloads.ts +++ b/src/agents/pi-embedded-runner/run/payloads.ts @@ -189,7 +189,12 @@ export function buildEmbeddedRunPayloads(params: { errorLower.includes("must be") || errorLower.includes("must have") || errorLower.includes("needs") || - errorLower.includes("requires"); + errorLower.includes("requires") || + // Treat command exit codes as internal - the model sees them and can decide how to proceed. + // Exit code 1 from grep/find/etc. just means "no matches", not a real error. + errorLower.includes("exited with code") || + errorLower.includes("exit code") || + errorLower.includes("aborted"); // Show tool errors only when: // 1. There's no user-facing reply AND the error is not recoverable