fix(whatsapp): sanitize tool XML and hide configured error text#71830
Conversation
Greptile SummaryThis PR sanitizes XML tool/function-call payloads from WhatsApp-visible text across auto-reply, channel outbound, and
Confidence Score: 4/5Safe to merge for single-account setups; multi-account deployments using per-account exposeErrorText overrides will not have errors suppressed in auto-reply flows. One P1 defect: account-level exposeErrorText override is not honored in the inbound auto-reply dispatch path, creating a behavioral gap vs. the sendPayload path. No P0s found. All other changes (XML sanitization, schema, docs) look correct. extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts — line 289 needs resolveMergedWhatsAppAccountConfig instead of direct config read. Prompt To Fix All With AIThis is a comment left during a code review.
Path: extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts
Line: 289
Comment:
**Account-level `exposeErrorText` overrides ignored in auto-reply path**
`exposeErrorText` is read directly from `params.cfg.channels?.whatsapp`, bypassing account-level inheritance. A user who sets `channels.whatsapp.accounts.work.exposeErrorText = false` while leaving the root flag at its default (`true`) will still have error text delivered through the auto-reply path, even though the `sendPayload` path (via `resolveMergedWhatsAppAccountConfig`) correctly suppresses it for the same account. `params.route.accountId` is already in scope (it's passed to `resolveChunkMode` on the line above), so the fix parallels what `outbound-base.ts` does:
```typescript
const exposeErrorText =
resolveMergedWhatsAppAccountConfig({ cfg: params.cfg, accountId: params.route.accountId })
.exposeErrorText !== false;
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(whatsapp): sanitize XML tool text an..." | Re-trigger Greptile |
| @@ -278,6 +286,7 @@ export async function dispatchWhatsAppBufferedReply(params: { | |||
| }); | |||
| const mediaLocalRoots = getAgentScopedMediaLocalRoots(params.cfg, params.route.agentId); | |||
| const disableBlockStreaming = resolveWhatsAppDisableBlockStreaming(params.cfg); | |||
| const exposeErrorText = params.cfg.channels?.whatsapp?.exposeErrorText !== false; | |||
There was a problem hiding this comment.
Account-level
exposeErrorText overrides ignored in auto-reply path
exposeErrorText is read directly from params.cfg.channels?.whatsapp, bypassing account-level inheritance. A user who sets channels.whatsapp.accounts.work.exposeErrorText = false while leaving the root flag at its default (true) will still have error text delivered through the auto-reply path, even though the sendPayload path (via resolveMergedWhatsAppAccountConfig) correctly suppresses it for the same account. params.route.accountId is already in scope (it's passed to resolveChunkMode on the line above), so the fix parallels what outbound-base.ts does:
const exposeErrorText =
resolveMergedWhatsAppAccountConfig({ cfg: params.cfg, accountId: params.route.accountId })
.exposeErrorText !== false;Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts
Line: 289
Comment:
**Account-level `exposeErrorText` overrides ignored in auto-reply path**
`exposeErrorText` is read directly from `params.cfg.channels?.whatsapp`, bypassing account-level inheritance. A user who sets `channels.whatsapp.accounts.work.exposeErrorText = false` while leaving the root flag at its default (`true`) will still have error text delivered through the auto-reply path, even though the `sendPayload` path (via `resolveMergedWhatsAppAccountConfig`) correctly suppresses it for the same account. `params.route.accountId` is already in scope (it's passed to `resolveChunkMode` on the line above), so the fix parallels what `outbound-base.ts` does:
```typescript
const exposeErrorText =
resolveMergedWhatsAppAccountConfig({ cfg: params.cfg, accountId: params.route.accountId })
.exposeErrorText !== false;
```
How can I resolve this? If you propose a fix, please make it concise.a63b5df to
2e9485b
Compare
|
Codex review: keeping this open for maintainer follow-up; there is still a little grit to resolve. Current main still lacks the PR's central WhatsApp sanitizer and error-visibility behavior. The active PR is a focused implementation candidate for the production report in #71715, and the provided PR context shows the earlier account-level override review issue was addressed on the branch. Best possible solution: Keep this PR open for maintainer review and land this branch or an equivalent focused patch. The right boundary is WhatsApp-owned visible-text normalization across auto-reply, channel outbound, and sendPayload; documented channels.whatsapp.exposeErrorText with account inheritance; and the minimal generic outbound opt-in needed so text-only error payloads can reach channel-owned suppression without false delivered-message bookkeeping. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against b85edb3f0cf6. |
2e9485b to
5dea76c
Compare
5dea76c to
bfdbf6b
Compare
4e880dc to
96cc131
Compare
96cc131 to
184d4a2
Compare
|
Merged via squash.
Thanks @rubencu! |
…claw#71830) Merged via squash. Prepared head SHA: 184d4a2 Co-authored-by: rubencu <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…claw#71830) Merged via squash. Prepared head SHA: 184d4a2 Co-authored-by: rubencu <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…claw#71830) Merged via squash. Prepared head SHA: 184d4a2 Co-authored-by: rubencu <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…claw#71830) Merged via squash. Prepared head SHA: 184d4a2 Co-authored-by: rubencu <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…text The Nextcloud Talk outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered chat messages. The same one-line sanitizeText hook was already shipped to 13 sibling channels (mattermost openclaw#98693, feishu openclaw#98705, twitch openclaw#103109, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text) to the outbound base adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Nostr outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Nostr DMs. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText to the NostrOutboundAdapter type and adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Zalo outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Zalo messages. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text) to the outbound adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Nostr outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Nostr DMs. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText to the NostrOutboundAdapter type and adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Zalo outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Zalo messages. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text) to the outbound adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Zalo outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Zalo messages. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text) to the outbound adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Nostr outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Nostr DMs. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText to the NostrOutboundAdapter type and adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Nostr outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Nostr DMs. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText to the NostrOutboundAdapter type and adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Zalo outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Zalo messages. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text) to the outbound adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Zalo outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Zalo messages. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text) to the outbound adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Nostr outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Nostr DMs. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText to the NostrOutboundAdapter type and adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Nostr outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Nostr DMs. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText to the NostrOutboundAdapter type and adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
The Nostr outbound adapter had no sanitizeText hook, so internal assistant tool-trace scaffolding (e.g. "⚠️ 🛠️ `exec (agent)` failed", raw <tool_call>/<function_response> XML) leaked verbatim into delivered Nostr DMs. The same one-line sanitizeText hook was already shipped to 13 sibling channels (twitch openclaw#103109, mattermost openclaw#98693, feishu openclaw#98705, signal openclaw#97360, slack openclaw#97367, matrix openclaw#97372, irc openclaw#97214, telegram, googlechat openclaw#90684, qqbot openclaw#90132, discord, whatsapp openclaw#71830, sms). Add sanitizeText to the NostrOutboundAdapter type and adapter, matching those precedents. Co-Authored-By: Claude <[email protected]>
Fixes #71715
Summary
sendPayloadpaths;channels.whatsapp.exposeErrorTextsupports channel and account-level configuration; text-only error payloads can route through channelsendPayloadwhen an adapter opts in.exposeErrorTextdefaults totruefor compatibility.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
sendPayload, so WhatsApp could not apply channel-owned error visibility policy on those payloads.sendPayload, plural<function_calls>/<tool_calls>wrappers, or no-op suppressed error deliveries.[email protected]with WhatsApp and provider fallback paths.Regression Test Plan (if applicable)
src/shared/text/assistant-visible-text.test.ts,extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.test.ts,extensions/whatsapp/src/auto-reply/deliver-reply.test.ts,extensions/whatsapp/src/channel-outbound.test.ts,extensions/whatsapp/src/outbound-adapter.sendpayload.test.ts,src/infra/outbound/deliver.test.ts,src/config/zod-schema.providers-whatsapp.test.ts,extensions/whatsapp/src/config-schema.test.ts.exposeErrorText: falsesuppresses WhatsApp error payload text without recording a false delivery.User-visible / Behavior Changes
channels.whatsapp.exposeErrorTextandchannels.whatsapp.accounts.<id>.exposeErrorText.true; setting it tofalsesuppresses WhatsApp error payload text.Diagram (if applicable)
Security Impact (required)
Yes/No) NoYes/No) NoYes/No) NoYes/No) NoYes/No) NoYes, explain risk + mitigation: N/ARepro + Verification
Environment
channels.whatsapp.exposeErrorText: falseand account-level override scenariosSteps
<function_calls><invoke ...></invoke></function_calls>.isError: truewhilechannels.whatsapp.exposeErrorTextisfalse.sendPayload, and core delivery paths.Expected
exposeErrorTextis disabled.Actual
Evidence
Regression tests were added for the issue scenarios and pass after the fix. Issue #71715 contains the production reproduction report.
Human Verification (required)
What you personally verified (not just CI), and how:
sendPayloadsuppression, config schema/defaults, core no-op delivery handling, and generated config/API baselines.<function_calls>/<tool_calls>wrappers, nested JSON containing closing tag text inside strings, payloads that sanitize to empty visible text, account-levelexposeErrorTextoverrides, and suppressed no-op delivery results.Review Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes/No) YesYes/No) YesYes/No) Nochannels.whatsapp.exposeErrorText: false.Risks and Mitigations
AI Assistance
pnpm check:changed, generated baseline checks, localcodex review --base origin/main, and GitHub CI.