openclaw - ✅(Solved) Fix [Bug]: imessage echo loop [2 pull requests, 2 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#59363Fetched 2026-04-08 02:25:28
View on GitHub
Comments
2
Participants
1
Timeline
9
Reactions
0
Participants
Timeline (top)
commented ×2cross-referenced ×2labeled ×2subscribed ×2
  • 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.

Root Cause

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.

Fix Action

Fix / Workaround

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:

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

PR fix notes

PR #59386: iMessage: harden echo detection against control-char prefixes

Description (problem / solution / changelog)

  • Strip leading/trailing control characters from echo text normalization
  • Add test for control char stripping in echo cache
  • Fixes issue with reflected payloads containing \u0000 etc.

Summary

Problem: iMessage assistant outbound messages were being reflected back as inbound user messages, causing infinite echo loops in direct chats. Root Cause: Echo detection failed to match reflected messages containing leading control characters (e.g., \u0000) prefixed to the original text. Fix: Enhanced text normalization in echo cache to strip leading/trailing control characters, ensuring detection works even with garbled reflections. Impact: Prevents feedback loops without affecting normal conversation flow.

  • Problem: -Why it matters: Echo loops waste API tokens, cause agent confusion, and disrupt user workflows by generating unwanted responses. -What changed: Modified normalizeEchoTextKey in echo-cache.ts to remove control characters (\u0000-\u001F, \u007F-\u009F) from text before comparison. Added unit test for control character stripping. -What did NOT change (scope boundary): No changes to message routing logic, permission checks, or other channels. Echo detection remains scoped to iMessage only.

Change Type (select all)

  • [ . ] Bug fix
  • [ . ] Security hardening

Scope (select all touched areas)

  • [ . ] Integrations

Linked Issue/PR

  • Closes #59363

Root Cause / Regression History (if applicable)

Root cause: Echo cache normalization did not account for control characters added by iMessage reflection, causing text mismatch and allowing echoes to pass through.

Missing detection / guardrail: No stripping of non-whitespace control characters in text normalization.

Prior context: Issue started with version 2026.3.31, likely due to changes in iMessage handling or reflection behavior.

Why this regressed now: Unknown, but control-char prefixes in reflections were not anticipated in original echo logic.

What was ruled out: Not a change in is_from_me detection (code already drops all self-messages); confirmed as echo detection gap.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this: Unit test
  • Target test or file: monitor-provider.echo-cache.test.ts
  • Scenario the test should lock in: Echo detection must match text even with leading/trailing control characters.
  • Why this is the smallest reliable guardrail: Unit test directly validates the normalization logic without requiring full iMessage setup.
  • Existing test that already covers this (if any): N/A
  • If no new test is added, why not: Test was added in this PR.

User-visible / Behavior Changes

  • None. This is an internal fix to echo detection; user conversations remain unchanged.

Diagram (if applicable)

N/A

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)

Repro + Verification

Environment

  • OS: macOS Tahoe 26.3
  • Runtime/container: npm global install under Homebrew Node
  • Model/provider: google/gemini-3.1-flash-lite-preview, openrouter/qwen/qwen3.6-plus-preview:free
  • Integration/channel (if any): iMessage (Apple Messages)
  • Relevant config (redacted): iMessage channel enabled

Steps

  1. Send message to agent in iMessage direct chat
  2. Agent responds
  3. Agent receives its own response back as user input (with control chars)
  4. Loop continues until manually stopped

Expected : Agent does not see its own messages; smooth conversation.

Actual : Echo loop with agent replying to self.

Evidence : Session logs show self-addressed routes and control-char prefixed reflections. Local hotfix (force-drop is_from_me) resolved it.

Human Verification (required)

What you personally verified (not just CI), and how:

Verified scenarios:

Code compiles without errors. Unit tests pass for echo cache normalization. Logic review confirms control char stripping matches issue description.

Edge cases checked:

Empty strings, strings with only control chars, mixed content.

What you did not verify:

Full iMessage integration test (no macOS/iMessage environment available). End-to-end echo loop reproduction.

Review Conversations

N/A (new PR)

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Risks and Mitigations

None. This is a targeted fix to text normalization with no side effects on other logic.

Changed files

  • .env.example (modified, +22/-0)
  • README.md (modified, +38/-0)
  • extensions/imessage/src/monitor/echo-cache.ts (modified, +5/-1)
  • extensions/imessage/src/monitor/monitor-provider.echo-cache.test.ts (modified, +11/-0)
  • src/cli/gateway-cli/run.ts (modified, +4/-0)
  • src/gateway/server-http.ts (modified, +239/-710)
  • src/gateway/server/events-http.ts (added, +283/-0)
  • src/gateway/server/ws-connection.ts (modified, +79/-47)
  • ui/vite.config.ts (modified, +15/-2)

PR #1: fix(imessage): drop all is_from_me messages unconditionally

Description (problem / solution / changelog)

Problem

iMessage reflects outbound agent messages back as inbound user messages (with control-char prefixes in some cases), causing echo reply loops. Closes #59363 upstream.

Root cause

resolveIMessageInboundDecision had a special path for self-chat where is_from_me=true messages would only be dropped if the echo-cache matched. When the cache missed (e.g. empty sentText on media-only sends, or control-char prefixed reflections), the message passed through as a user message and triggered another agent reply.

Fix

Drop all is_from_me messages unconditionally — matching the behaviour that already existed for non-self-chat DMs and groups. The selfChatCache.remember() call is preserved so the subsequent is_from_me=false SQLite reflection is still caught by the selfChatCache.has() check.

Files changed

  • extensions/imessage/src/monitor/inbound-processing.ts: collapse self-chat branch into unconditional drop

Fixes: https://github.com/openclaw/openclaw/issues/59363

Changed files

  • .agent/workflows/update_clawdbot.md (removed, +0/-380)
  • .agents/archive/PR_WORKFLOW_V1.md (removed, +0/-181)
  • .agents/archive/merge-pr-v1/SKILL.md (removed, +0/-304)
  • .agents/archive/merge-pr-v1/agents/openai.yaml (removed, +0/-4)
  • .agents/archive/prepare-pr-v1/SKILL.md (removed, +0/-336)
  • .agents/archive/prepare-pr-v1/agents/openai.yaml (removed, +0/-4)
  • .agents/archive/review-pr-v1/SKILL.md (removed, +0/-253)
  • .agents/archive/review-pr-v1/agents/openai.yaml (removed, +0/-4)
  • .agents/maintainers.md (added, +1/-0)
  • .agents/skills/PR_WORKFLOW.md (removed, +0/-249)
  • .agents/skills/merge-pr/SKILL.md (removed, +0/-99)
  • .agents/skills/merge-pr/agents/openai.yaml (removed, +0/-4)
  • .agents/skills/mintlify/SKILL.md (removed, +0/-345)
  • .agents/skills/openclaw-ghsa-maintainer/SKILL.md (added, +87/-0)
  • .agents/skills/openclaw-parallels-smoke/SKILL.md (added, +119/-0)
  • .agents/skills/openclaw-pr-maintainer/SKILL.md (added, +75/-0)
  • .agents/skills/openclaw-qa-testing/SKILL.md (added, +85/-0)
  • .agents/skills/openclaw-qa-testing/agents/openai.yaml (added, +4/-0)
  • .agents/skills/openclaw-release-maintainer/SKILL.md (added, +267/-0)
  • .agents/skills/openclaw-test-heap-leaks/SKILL.md (added, +75/-0)
  • .agents/skills/openclaw-test-heap-leaks/agents/openai.yaml (added, +4/-0)
  • .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs (added, +553/-0)
  • .agents/skills/parallels-discord-roundtrip/SKILL.md (added, +62/-0)
  • .agents/skills/prepare-pr/SKILL.md (removed, +0/-122)
  • .agents/skills/prepare-pr/agents/openai.yaml (removed, +0/-4)
  • .agents/skills/review-pr/SKILL.md (removed, +0/-142)
  • .agents/skills/review-pr/agents/openai.yaml (removed, +0/-4)
  • .agents/skills/security-triage/SKILL.md (added, +111/-0)
  • .codex (renamed, +0/-0)
  • .detect-secrets.cfg (modified, +19/-4)
  • .dockerignore (modified, +12/-0)
  • .gitattributes (modified, +2/-0)
  • .github/CODEOWNERS (added, +54/-0)
  • .github/FUNDING.yml (removed, +0/-1)
  • .github/ISSUE_TEMPLATE/bug_report.yml (modified, +74/-21)
  • .github/ISSUE_TEMPLATE/config.yml (modified, +2/-2)
  • .github/ISSUE_TEMPLATE/feature_request.yml (modified, +1/-1)
  • .github/actionlint.yaml (modified, +8/-2)
  • .github/actions/ensure-base-commit/action.yml (added, +61/-0)
  • .github/actions/setup-node-env/action.yml (modified, +44/-28)
  • .github/actions/setup-pnpm-store-cache/action.yml (modified, +40/-5)
  • .github/codeql/codeql-javascript-typescript.yml (added, +18/-0)
  • .github/dependabot.yml (modified, +26/-12)
  • .github/labeler.yml (modified, +125/-16)
  • .github/pr-assets/compaction-checkpoints/sessions-checkpoints-inline.png (added, +0/-0)
  • .github/pr-assets/compaction-checkpoints/sessions-overview-inline.png (added, +0/-0)
  • .github/pull_request_template.md (modified, +46/-7)
  • .github/workflows/auto-response.yml (modified, +318/-8)
  • .github/workflows/ci.yml (modified, +967/-424)
  • .github/workflows/codeql.yml (added, +137/-0)
  • .github/workflows/control-ui-locale-refresh.yml (added, +172/-0)
  • .github/workflows/docker-release.yml (modified, +304/-68)
  • .github/workflows/docs-sync-publish.yml (added, +70/-0)
  • .github/workflows/docs-translate-trigger-release.yml (added, +42/-0)
  • .github/workflows/install-smoke.yml (modified, +176/-28)
  • .github/workflows/labeler.yml (modified, +444/-86)
  • .github/workflows/macos-release.yml (added, +93/-0)
  • .github/workflows/openclaw-npm-release.yml (added, +449/-0)
  • .github/workflows/plugin-clawhub-release.yml (added, +276/-0)
  • .github/workflows/plugin-npm-release.yml (added, +217/-0)
  • .github/workflows/sandbox-common-smoke.yml (modified, +11/-3)
  • .github/workflows/stale.yml (modified, +173/-7)
  • .github/workflows/workflow-sanity.yml (modified, +59/-3)
  • .gitignore (modified, +59/-0)
  • .jscpd.json (added, +16/-0)
  • .mailmap (added, +13/-0)
  • .markdownlint-cli2.jsonc (modified, +3/-0)
  • .npmignore (added, +3/-0)
  • .npmrc (modified, +4/-1)
  • .oxfmtrc.jsonc (modified, +5/-0)
  • .oxlintrc.json (modified, +32/-2)
  • .pi/extensions/diff.ts (modified, +11/-89)
  • .pi/extensions/files.ts (modified, +22/-82)
  • .pi/extensions/prompt-url-widget.ts (modified, +13/-16)
  • .pi/extensions/ui/paged-select.ts (added, +82/-0)
  • .pi/prompts/landpr.md (modified, +5/-5)
  • .pi/prompts/reviewpr.md (modified, +37/-8)
  • .pre-commit-config.yaml (modified, +54/-2)
  • .prettierignore (added, +1/-0)
  • .secrets.baseline (modified, +229/-316)
  • .swiftformat (modified, +1/-1)
  • .swiftlint.yml (modified, +3/-1)
  • AGENTS.md (modified, +185/-102)
  • CHANGELOG.md (modified, +3798/-385)
  • CLAUDE.md (modified, +1/-1)
  • CONTRIBUTING.md (modified, +83/-7)
  • Dockerfile (modified, +243/-29)
  • Dockerfile.sandbox (modified, +8/-4)
  • Dockerfile.sandbox-browser (modified, +10/-6)
  • Dockerfile.sandbox-common (modified, +7/-4)
  • Makefile (added, +4/-0)
  • README.md (modified, +146/-82)
  • SECURITY.md (modified, +210/-3)
  • Swabble/Sources/SwabbleKit/WakeWordGate.swift (modified, +7/-13)
  • Swabble/Tests/SwabbleKitTests/WakeWordGateTests.swift (modified, +19/-0)
  • appcast.xml (modified, +398/-272)
  • apps/android/README.md (modified, +257/-14)
  • apps/android/THIRD_PARTY_LICENSES/MANROPE_OFL.txt (added, +93/-0)
  • apps/android/app/build.gradle.kts (modified, +271/-131)
  • apps/android/app/proguard-rules.pro (modified, +0/-20)

Code Example

# 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.
RAW_BUFFERClick to expand / collapse

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.

extent analysis

TL;DR

To fix the iMessage echo bug, patch the upstream iMessage monitor to prevent inbound is_from_me events from dispatching and harden reflection detection against control-char-prefixed reflected payloads.

Guidance

  • The root cause of the issue is that is_from_me messages can continue if the echo-cache match is missed, causing reflected assistant text to dispatch as inbound.
  • To mitigate this, force-drop all inbound is_from_me iMessage events in the resolveIMessageInboundDecision function.
  • The local hotfix that resolved the issue involved modifying the monitor-provider-ClqFz1Hj.js file to return a "drop" response for is_from_me messages.
  • To verify the fix, test the iMessage conversation flow and ensure that the agent no longer replies to its own messages.

Example

The local hotfix code snippet is:

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

This code drops all inbound is_from_me events, preventing the echo loop.

Notes

The provided hotfix is a temporary solution and may be overwritten by future OpenClaw updates. A permanent fix requires patching the upstream iMessage monitor.

Recommendation

Apply the workaround by modifying the monitor-provider-ClqFz1Hj.js file to include the hotfix code snippet, as this will prevent the echo loop and allow for smooth conversations.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

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.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: imessage echo loop [2 pull requests, 2 comments, 1 participants]