Summary
When OpenClaw's iMessage channel is used in a self-chat scenario (owner messaging their own iMessage address), every message creates two entries in the Messages database with different is_from_me values but identical text and timestamps. The existing is_from_me:true filter drops one copy, but the is_from_me:false duplicate passes through and gets processed as a new inbound message — creating an echo loop.
Reproduction
- Configure the iMessage channel with
imsg CLI
- Send a message to yourself (e.g. from your iPhone to the same iCloud address the Mac is signed into)
- The agent receives the message, replies
- The reply generates two database entries:
is_from_me:true (correctly dropped by the handler at reply-XaR8IPbY.js:40925)
is_from_me:false (passes through as a new inbound message → echo)
- This creates infinite echo loops
Evidence from imsg rpc debug log
Same message, 2 seconds apart, different GUIDs, different is_from_me:
// is_from_me:true — correctly dropped
{"params":{"message":{"id":9641,"is_from_me":true,"text":"Do you want to report this issue?","created_at":"2026-03-02T20:58:10.649Z","guid":"0126C9E6-..."}},"method":"message"}
// is_from_me:false — echo duplicate, NOT dropped
{"params":{"message":{"id":9642,"is_from_me":false,"text":"Do you want to report this issue?","created_at":"2026-03-02T20:58:10.649Z","guid":"35C54324-..."}},"method":"message"}
Suggested fix
For self-chat detection, when both sender and destination_caller_id are the same address AND an is_from_me:true message was recently seen with the same text + created_at, drop the is_from_me:false duplicate.
Alternatively, track recently sent message GUIDs or text hashes and dedup within a short time window.
Also affects BlueBubbles channel
The BlueBubbles channel has the same issue — the webhook fires for both sent and received copies of self-chat messages.
Additional bug: Control UI blocks webhook POST requests
While debugging this, we discovered that handleControlUiHttpRequest() in the gateway rejects ALL non-GET/HEAD HTTP requests with 405 before checking the request path. This blocks webhook delivery for any webhook-based channel (BlueBubbles, potentially Telegram, Google Chat) when the Control UI is enabled.
The method check at the top of the function should be moved after the basePath / pathname matching, so only Control UI paths reject POST.
Environment
- OpenClaw: latest (npm, 2026-03-02)
- macOS: Tahoe 26.3.0 (arm64)
- imsg: 0.5.0
- BlueBubbles: 1.9.9
Summary
When OpenClaw's iMessage channel is used in a self-chat scenario (owner messaging their own iMessage address), every message creates two entries in the Messages database with different
is_from_mevalues but identical text and timestamps. The existingis_from_me:truefilter drops one copy, but theis_from_me:falseduplicate passes through and gets processed as a new inbound message — creating an echo loop.Reproduction
imsgCLIis_from_me:true(correctly dropped by the handler atreply-XaR8IPbY.js:40925)is_from_me:false(passes through as a new inbound message → echo)Evidence from
imsg rpcdebug logSame message, 2 seconds apart, different GUIDs, different
is_from_me:Suggested fix
For self-chat detection, when both
senderanddestination_caller_idare the same address AND anis_from_me:truemessage was recently seen with the same text +created_at, drop theis_from_me:falseduplicate.Alternatively, track recently sent message GUIDs or text hashes and dedup within a short time window.
Also affects BlueBubbles channel
The BlueBubbles channel has the same issue — the webhook fires for both sent and received copies of self-chat messages.
Additional bug: Control UI blocks webhook POST requests
While debugging this, we discovered that
handleControlUiHttpRequest()in the gateway rejects ALL non-GET/HEAD HTTP requests with 405 before checking the request path. This blocks webhook delivery for any webhook-based channel (BlueBubbles, potentially Telegram, Google Chat) when the Control UI is enabled.The method check at the top of the function should be moved after the
basePath/ pathname matching, so only Control UI paths reject POST.Environment