← Back to PRs

#17552: fix(agents): suppress tool error warnings when assistant already replied

by AytuncYildizli open 2026-02-15 22:07 View on GitHub →
channel: voice-call gateway commands agents size: S
## Summary Fixes #9651 When the assistant composes a user-facing reply, `shouldShowToolErrorWarning` now returns `false` — the error is **not** surfaced as a separate `⚠️ … failed` payload. Previously, the function unconditionally returned `true` for any tool in `MUTATING_TOOL_NAMES`, bypassing the `hasUserFacingReply` check entirely. Because `exec` and `bash` are in `MUTATING_TOOL_NAMES`, every non-zero exit — including perfectly normal ones like `grep` returning code 1 (no match) or `find` with no results — produced a warning message that leaked to messaging channels (Telegram, WhatsApp, Discord) as a separate chat bubble. ### Root cause ```typescript // Before (payloads.ts — shouldShowToolErrorWarning) const isMutatingToolError = params.lastToolError.mutatingAction ?? isLikelyMutatingToolName(params.lastToolError.toolName); if (isMutatingToolError) { return true; // ← bypasses hasUserFacingReply entirely } ``` `isMutatingToolCall` unconditionally returns `true` for `exec`/`bash` (tool-mutation.ts:107-108), so `buildToolMutationState` always sets `mutatingAction = true`, and the early return fires on every failed command. ### Fix - **`payloads.ts`**: Move the `hasUserFacingReply` check **above** the mutating-tool branch. When the assistant already addressed the error in its reply, duplicating it as a separate chat bubble is noise. When no reply exists, mutating-tool errors are still always surfaced as the sole failure signal. - **`handlers.tools.ts`**: Guard `emitToolOutput` with `!isToolError` so error payloads are not emitted as regular tool output on verbose channels — matching the existing guard already used for media delivery (line 279). ### What changes | Scenario | Before | After | |----------|--------|-------| | `exec` fails, assistant replies with explanation | `⚠️ Exec: … failed` **leaked** as separate message | Suppressed — assistant reply is sufficient | | `write` fails, assistant replies "Done." | Duplicate error bubble | Suppressed — assistant already replied | | `write` fails, **no** assistant reply | Error shown | Error shown (unchanged) | | `message send` fails, **no** assistant reply | Error shown | Error shown (unchanged) | | Non-mutating tool fails, no reply | Error shown | Error shown (unchanged) | | `suppressToolErrors` enabled, mutating, no reply | Error shown | Error shown (unchanged) | ### Local validation - `pnpm build` — clean ✅ - `pnpm check` — clean ✅ - `pnpm test` — all **5,136 unit tests** pass ✅ - **21 e2e tests** pass ✅ - Tested locally on a production OpenClaw instance (Mac Studio, WhatsApp channel) — the duplicate `⚠️ Exec: … failed` bubbles no longer appear when the assistant has already replied with an explanation. ### Tests - Updated the existing test that validated the old behavior ("shows mutating tool errors even when assistant output exists" → "suppresses mutating tool errors when the assistant already composed a reply") - Added test: "shows mutating tool errors when the assistant has no user-facing reply" - Added test: "suppresses exec tool errors when the assistant already replied" — reproduces the exact scenario from #9651 ### Scope Single focused change: fix the duplicate error-message leak on messaging channels. Two files modified (`payloads.ts`, `handlers.tools.ts`), both addressing the same root cause from different angles. ### AI-assistance transparency - [x] **AI-assisted**: This PR was authored with the help of Claude (Anthropic) via [OpenClaw](https://github.com/openclaw/openclaw) — specifically, the `exec` tool error was discovered during daily usage of an OpenClaw agent on WhatsApp, and the fix was developed interactively. - [x] **Testing level**: Fully tested — all existing unit tests updated, 3 new targeted tests added, full `pnpm build && pnpm check && pnpm test` suite passes, and the fix was validated on a live production instance. - [x] **Understanding**: The contributor ([@AytuncYildizli](https://github.com/AytuncYildizli)) understands the logic change and has been running OpenClaw with this patch in production since Feb 14, 2026 without regressions. ### Related - #9651 — Exec tool error output sent as message to WhatsApp/Telegram (this PR fixes it) - #11111 — Same issue on Discord (closed as dup of #9651) - #10261 — Prior partial fix attempt (addresses `emitToolOutput` but not `shouldShowToolErrorWarning`) <!-- greptile_comment --> <h3>Greptile Summary</h3> Fixes duplicate error messages on messaging channels by prioritizing assistant replies over raw tool errors. Previously, mutating tool errors (like `exec` or `bash` non-zero exits) always generated separate warning bubbles even when the assistant already explained the situation - this was especially noisy on WhatsApp/Telegram/Discord where each payload becomes a distinct chat bubble. **Key changes:** - `payloads.ts`: Reordered `shouldShowToolErrorWarning` logic to check `hasUserFacingReply` before the mutating-tool branch, suppressing duplicate error warnings when assistant has already replied - `handlers.tools.ts`: Added `!isToolError` guard to `emitToolOutput` preventing error payloads from being emitted as regular tool output (matches existing guard on line 294 for media delivery) - Tests updated to reflect new behavior with 3 new test cases covering the exact scenarios from issue #9651 The fix maintains the safety guarantee: mutating tool errors are still surfaced when there's NO assistant reply (the only failure signal). <h3>Confidence Score: 5/5</h3> - Safe to merge - focused fix with comprehensive test coverage and production validation - The logic change is well-reasoned and addresses the root cause correctly. All existing tests pass (5,136 unit + 21 e2e), new tests cover the exact scenarios, and the author has run this in production since Feb 14. The change maintains safety by still showing errors when there's no assistant reply. - No files require special attention <sub>Last reviewed commit: 9f7eca5</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs