open-webui / open-webui/computer
bug: messaging bridge drops final replies on send-only platforms (WhatsApp Cloud API)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 569
- Forks
- 79
- PR merge metrics
- No merged PRs in 30d
Description
Bug description
When a bot is configured on a platform whose API cannot edit sent messages (WhatsApp via the Meta Cloud API), the messaging bridge processes inbound messages and runs the agent task correctly, but the final reply is never delivered. The user only ever receives the initial "Thinking..." placeholder message. The reply text exists in the cptr chat thread — only the platform-side delivery is missing, and the failure is logged at DEBUG level only, so it is invisible in normal operation.
Root cause
cptr/utils/bridge.py → _stream_loop() implements reply delivery with an edit-based model: it sends a placeholder message, streams progress via adapter.edit(), and delivers the final response via adapter.edit() on that placeholder.
WhatsApp Cloud API is send-only — the Messages API exposes exactly one endpoint (POST /{Phone-Number-ID}/messages); sent messages cannot be edited (Meta's "edit messages" docs cover inbound user-edit webhook events only). WhatsAppAdapter.edit() is therefore a no-op. Since the placeholder send always succeeds, the send-based fallback branch (reachable only when there is no platform_msg_id) never runs, so the final edit silently drops every normal-length reply.
Two related defects in the same code path:
- Multi-chunk replies are truncated on all edit-fallback platforms. The overflow-delivery branch ends its chunk loop with an unconditional
break, so everything after the first chunk is silently discarded. - The empty-reply branch ("task complete, no text output") calls
edit()directly, which is also a no-op on send-only platforms. - Typing indicator sends HTTP 400 on every call.
WhatsAppAdapter.send_typing()posts a hardcoded fakemessage_id; the Cloud API requires the real inboundwamidfrom the messages webhook for the read-receipt/typing-indicator endpoint. Because the bridge callssend_typing()each poll iteration, every agent run produces a burst of 400s (exception swallowed).
To Reproduce
- Create a bot with
platform: "whatsapp"and a validaccess_token|phone_number_idtoken, with an allowed sender. - Message the bot's WhatsApp number from that sender.
- Observe: the "Thinking..." placeholder arrives; the final agent reply never does (any reply under the chunk limit). The reply text is present in the cptr chat thread.
- Optionally watch the server logs: repeated 400s from the typing-indicator endpoint during the run.
Expected behavior
- Platforms that cannot edit messages should receive the final reply via
send()(in chunks), with no placeholder message (it could never be updated in place). - The WhatsApp typing indicator should use the documented payload:
POST /{phone_number_id}/messageswith{"messaging_product": "whatsapp", "status": "read", "message_id": "<inbound wamid>", "typing_indicator": {"type": "text"}}(requires Graph API ≥ v23.0; the indicator auto-dismisses after 25 seconds or on reply, so periodic refresh is intended usage). This requires tracking the last inboundmessage.idfrom the webhook payload. - Failed final deliveries should be logged at WARNING+ with the exception, not DEBUG.
Suggested approach
- Add a capability flag on
BaseAdapter(e.g.supports_edit, defaultTrue;WhatsAppAdaptersetsFalse). - In
_dispatch_task: skip the placeholder when the adapter cannot edit. - In
_stream_loop: when the adapter cannot edit, skip streaming edits entirely, refresh the typing indicator each poll, and deliver the final reply viaadapter.send()chunks (also fixing the overflowbreakand the empty-reply branch). - If a final edit fails on an edit-capable platform, fall back to
send()instead of dropping the reply. - Adapter: bump
API_BASEto v23.0 (first GA version with typing indicators), implement the real typing indicator, track the last inbound message id from the webhook, and surface interactive button/list replies (currently dropped silently) as plain text.
Additional context
- The same fix also applies the observability correction: a dropped final reply is currently a DEBUG-level event; it should be impossible to lose a reply silently.
- Implemented and tested on a fork (capability flag + send-based delivery + real typing indicator + webhook hardening); happy to share implementation notes if useful.
Environment
- cptr 0.9.21 (latest main at time of writing)
- WhatsApp Cloud API (Meta), Graph API v21.0 as shipped in the adapter
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in cptr/utils/bridge.py at _dispatch_task and _stream_loop, then inspect BaseAdapter and WhatsAppAdapter. Trace the webhook handling for the inbound message id and the adapter's typing-indicator path. Done means send-only platforms deliver complete final replies, empty replies and failures are handled visibly, typing indicators use the inbound id, and interactive replies are surfaced as plain text.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend, observability
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100