fix(telegram): treat exec exit code errors as internal

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 <noreply@anthropic.com>
This commit is contained in:
Naveen Chatlapalli 2026-01-29 01:32:23 -06:00
parent da71eaebd2
commit e5547294f3
2 changed files with 26 additions and 6 deletions

View File

@ -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: [],

View File

@ -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