Skip to content

[Bug]: imessage echo loop #59363

Description

@fagandesign

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

iMessage reflects assistant outbound text back as inbound user messages in direct chat, causing loops.

Steps to reproduce

  1. send message to agent in imessage.
  2. agent sends message back
  3. agent receives its own message back
  4. agent keeps replying to its own messages
  5. eventually agent stops replying to self, but echo persists

Expected behavior

expected behavior is that the agent would not see its own message returned. there would be a smooth conversation with no filtering required.

Actual behavior

Evidence:

  1. Session metadata shows self-addressed route:
    ~/.openclaw/agents/main/sessions/sessions.json
    origin.from == origin.to == imessage:+xxxxxxxxxx

  2. In session log, assistant messages are re-ingested as user content with control-char prefixes (e.g. ��\u0000):
    ~/.openclaw/agents/main/sessions/6b18f336-84ff-4525-9736-32eaece59b25.jsonl

  3. Runtime decision path:
    /opt/homebrew/lib/node_modules/openclaw/dist/monitor-provider-ClqFz1Hj.js
    function resolveIMessageInboundDecision()
    around line ~917 (params.message.is_from_me branch)

Root cause:
For self-chat-shaped routing, is_from_me messages could continue if echo-cache match missed, so reflected assistant text could dispatch as inbound.

Local hotfix that resolved it:
Force-drop all inbound is_from_me iMessage events (including self-chat) in resolveIMessageInboundDecision:

if (params.message.is_from_me) {
params.selfChatCache?.remember(selfChatLookup);
return { kind: "drop", reason: isSelfChat ? "from me self-chat" : "from me" };
}

Request:
Please patch upstream iMessage monitor so inbound is_from_me cannot dispatch, and harden reflection detection against control-char-prefixed reflected payloads.

OpenClaw version

2026.3.31 / 2026.4.1

Operating system

macOS Tahoe 26.3

Install method

Install method: npm (global install) under Homebrew Node on macOS arm64; CLI at /opt/homebrew/bin/openclaw, package root /opt/homebrew/lib/node_modules/openclaw. Gateway launched as macOS LaunchAgent (ai.openclaw.gateway.plist).

Model

google/gemini-3.1-flash-lite-preview, openrouter/qwen/qwen3.6-plus-preview:free

Provider / routing chain

Provider / routing chain: iMessage (Apple Messages) -> local imsg CLI bridge (channels.imessage.cliPath="imsg", service="imessage") -> OpenClaw local gateway (LaunchAgent, ws://127.0.0.1:18789, no reverse proxy/tailscale) -> agent runtime -> model router/provider (OpenRouter; observed on qwen/qwen3.6-plus-preview:free during failures) -> response back through OpenClaw -> imsg send -> Apple Messages. Reflection path seen in this bug: outbound imsg send is re-emitted by imsg watch as inbound (is_from_me=true) in the same thread, then re-dispatched by OpenClaw.

Additional provider/model setup details

OpenClaw 2026.4.1 iMessage echo bug report

Issue:
iMessage reflects assistant outbound text back as inbound user messages in direct chat, causing loops.

Evidence:

  1. Session metadata shows self-addressed route:
    ~/.openclaw/agents/main/sessions/sessions.json
    origin.from == origin.to == imessage:+xxxxxxxxxx

  2. In session log, assistant messages are re-ingested as user content with control-char prefixes (e.g. ��\u0000):
    ~/.openclaw/agents/main/sessions/6b18f336-84ff-4525-9736-32eaece59b25.jsonl

  3. Runtime decision path:
    /opt/homebrew/lib/node_modules/openclaw/dist/monitor-provider-ClqFz1Hj.js
    function resolveIMessageInboundDecision()
    around line ~917 (params.message.is_from_me branch)

Root cause:
For self-chat-shaped routing, is_from_me messages could continue if echo-cache match missed, so reflected assistant text could dispatch as inbound.

Local hotfix that resolved it:
Force-drop all inbound is_from_me iMessage events (including self-chat) in resolveIMessageInboundDecision:

if (params.message.is_from_me) {
params.selfChatCache?.remember(selfChatLookup);
return { kind: "drop", reason: isSelfChat ? "from me self-chat" : "from me" };
}

Request:
Please patch upstream iMessage monitor so inbound is_from_me cannot dispatch, and harden reflection detection against control-char-prefixed reflected payloads.

Logs, screenshots, and evidence

# OpenClaw iMessage Echo Evidence (Redacted, Compact)

Generated: Wed Apr  1 22:57:18 EDT 2026

## Summary
- Direct iMessage thread intermittently reflected assistant outbound text back as inbound user input.
- Session metadata showed self-addressed routing shape (origin.from == origin.to).
- Reflected payloads often contained leading control chars (e.g., ���).
- Local mitigation (drop all inbound is_from_me events) stopped the loop in live testing.

## 1) Session Origin Metadata

{
  "key": "agent:main:main",
  "sessionId": "6b18f336-84ff-4525-9736-32eaece59b25",
  "chatType": "direct",
  "deliveryContext": {
    "channel": "imessage",
    "to": "imessage:<USER_E164>",
    "accountId": "default"
  },
  "origin": {
    "label": "imessage:<USER_E164>",
    "provider": "heartbeat",
    "surface": "imessage",
    "chatType": "direct",
    "from": "imessage:<USER_E164>",
    "to": "imessage:<USER_E164>",
    "accountId": "default"
  }
}


## 2) Reflected Inbound Sequence (selected events)

[2026-04-02T01:57:20.579Z] assistant: All good now — message came through clean, no echoes. Whatever was causing the loop, restarting the Messages app on your Mac broke it. 👻
[2026-04-02T01:57:21.304Z] user: Conversation info (untrusted metadata):  {   "message_id": "7274",   "sender_id": "<USER_E164>",   "sender": "<USER_E164>",   "timestamp": "Wed 2026-04-01 21:57 EDT",   "was_mentioned": true }   Sender (untrusted metadata):  {   "label": "+1…
[2026-04-02T01:57:24.141Z] assistant: NO_REPLY
[2026-04-02T01:57:46.538Z] user: Conversation info (untrusted metadata):  {   "message_id": "7276",   "sender_id": "<USER_E164>",   "sender": "<USER_E164>",   "timestamp": "Wed 2026-04-01 21:57 EDT",   "was_mentioned": true }   Sender (untrusted metadata):  {   "label": "+1…
[2026-04-02T01:57:49.488Z] assistant: NO_REPLY
[2026-04-02T01:58:16.193Z] user: Conversation info (untrusted metadata):  {   "message_id": "7278",   "sender_id": "<USER_E164>",   "sender": "<USER_E164>",   "timestamp": "Wed 2026-04-01 21:58 EDT",   "was_mentioned": true }   Sender (untrusted metadata):  {   "label": "+1…
[2026-04-02T01:58:18.533Z] assistant: NO_REPLY


## 3) Gateway Delivery Burst During Echo Window



## 4) Local Mitigation Diff

--- /opt/homebrew/lib/node_modules/openclaw/dist/monitor-provider-ClqFz1Hj.js.bak-20260401-221750-fromme-drop	2026-04-01 22:17:50
+++ /opt/homebrew/lib/node_modules/openclaw/dist/monitor-provider-ClqFz1Hj.js	2026-04-01 22:18:17
@@ -916,27 +916,9 @@
 	const hasInboundGuid = Boolean(normalizeReplyField(params.message.guid));
 	if (params.message.is_from_me) {
 		params.selfChatCache?.remember(selfChatLookup);
-		if (isSelfChat) {
-			const echoScope = buildIMessageEchoScope({
-				accountId: params.accountId,
-				isGroup,
-				chatId,
-				sender
-			});
-			if (params.echoCache && (bodyText || inboundMessageId) && hasIMessageEchoMatch({
-				echoCache: params.echoCache,
-				scope: echoScope,
-				text: bodyText || void 0,
-				messageIds: inboundMessageIds,
-				skipIdShortCircuit: !hasInboundGuid
-			})) return {
-				kind: "drop",
-				reason: "agent echo in self-chat"
-			};
-			skipSelfChatHasCheck = true;
-		} else return {
+		return {
 			kind: "drop",
-			reason: "from me"
+			reason: isSelfChat ? "from me self-chat" : "from me"
 		};
 	}
 	if (isGroup && !chatId) return {


## 5) Validation
- After applying the mitigation and restarting gateway, live tests no longer produced agent-side echo loops.
- This is a hotfix in installed dist code and may be overwritten by future openclaw update.

Impact and severity

Affected: imessage/agents
Severity: Annoying, even the agent said it was annoying. Blocks workflows because agent kept replying to self, data risk because agent was caught in a feedback loop and thought I had confirmed I wanted it to do whatever it was suggesting. Eventually the agent would calm down and stop messaging itself. I thought it was the model at first but after changing models it still persisted.
Frequency: always. every message.
Consequence: Feedback loop caused non-approved config edits, churn and gateway crashes, LOTS of api token usage.

Additional information

Pretty sure last known clean version without the bug was 2026.3.28 but it could have been an earlier one. It seems like this started with 2026.3.31.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingregressionBehavior that previously worked and now fails

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions