From f8adfcf60eb4331aa9b9c961e15bcc25c71408b6 Mon Sep 17 00:00:00 2001 From: Sebastian <19554889+sebslight@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:12:06 -0500 Subject: [PATCH] test(agents): cover exec non-zero exits --- CHANGELOG.md | 1 + src/agents/bash-tools.e2e.test.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57bcf70614b..de76fbbcdd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Docs: https://docs.openclaw.ai - Auto-reply/Subagents: propagate group context (`groupId`, `groupChannel`, `space`) when spawning via `/subagents spawn`, matching tool-triggered subagent spawn behavior. - Subagents: cap announce retry loops with max attempts and expiry to prevent infinite retry spam after deferred announces. (#18444) - Agents/Tools/exec: add a preflight guard that detects likely shell env var injection (e.g. `$DM_JSON`, `$TMPDIR`) in Python/Node scripts before execution, preventing recurring cron failures and wasted tokens when models emit mixed shell+language source. (#12836) +- Agents/Tools/exec: treat normal non-zero exit codes as completed and append the exit code to tool output to avoid false tool-failure warnings. (#18425) - Agents/Tools: make loop detection progress-aware and phased by hard-blocking known `process(action=poll|log)` no-progress loops, warning on generic identical-call repeats, warning + no-progress-blocking ping-pong alternation loops (10/20), coalescing repeated warning spam into threshold buckets (including canonical ping-pong pairs), adding a global circuit breaker at 30 no-progress repeats, and emitting structured diagnostic `tool.loop` warning/error events for loop actions. (#16808) Thanks @akramcodez and @beca-oc. - Agents/Tools: scope the `message` tool schema to the active channel so Telegram uses `buttons` and Discord uses `components`. (#18215) Thanks @obviyus. - Agents/Image tool: replace Anthropic-incompatible union schema with explicit `image` (single) and `images` (multi) parameters, keeping tool schemas `anyOf`/`oneOf`/`allOf`-free while preserving multi-image analysis support. (#18551, #18566) Thanks @aldoeliacim. diff --git a/src/agents/bash-tools.e2e.test.ts b/src/agents/bash-tools.e2e.test.ts index ac78a40ea29..2650f951cec 100644 --- a/src/agents/bash-tools.e2e.test.ts +++ b/src/agents/bash-tools.e2e.test.ts @@ -315,6 +315,36 @@ describe("exec tool backgrounding", () => { }); }); +describe("exec exit codes", () => { + const originalShell = process.env.SHELL; + + beforeEach(() => { + if (!isWin && defaultShell) { + process.env.SHELL = defaultShell; + } + }); + + afterEach(() => { + if (!isWin) { + process.env.SHELL = originalShell; + } + }); + + it("treats non-zero exits as completed and appends exit code", async () => { + const command = isWin + ? joinCommands(["Write-Output nope", "exit 1"]) + : joinCommands(["echo nope", "exit 1"]); + const result = await execTool.execute("call1", { command }); + + expect(result.details.status).toBe("completed"); + expect(result.details.exitCode).toBe(1); + + const text = normalizeText(result.content.find((c) => c.type === "text")?.text); + expect(text).toContain("nope"); + expect(text).toContain("Command exited with code 1"); + }); +}); + describe("exec notifyOnExit", () => { it("enqueues a system event when a backgrounded exec exits", async () => { const tool = createExecTool({