openclaw - ✅(Solved) Fix iMessage echo loop: DM incorrectly classified as self-chat, bypassing echo filter [2 pull requests, 1 comments, 2 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#59845Fetched 2026-04-08 02:39:52
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
cross-referenced ×2commented ×1mentioned ×1referenced ×1

Outbound iMessage DM replies echo back as new inbound messages, creating a feedback loop. The echo detection system exists but fails to catch these because the conversation is misclassified as a "self-chat."

Root Cause

In monitor-provider (~line 909), the self-chat detection logic:

const isSelfChat = !isGroup && chatIdentifier != null 
  && normalizeIMessageHandle(sender) === normalizeIMessageHandle(chatIdentifier);

In a normal DM, imsg reports sender as the peer handle (e.g. +1XXXXXXXXXX) regardless of message direction. The chatIdentifier for that DM is also the peer handle. So sender === chatIdentifier is always true, and isSelfChat is always true for all DMs.

This matters because when is_from_me: true + isSelfChat: true, the code enters the self-chat echo cache path (4s text TTL) instead of the normal is_from_me instant-drop path. Once the 4s cache expires, subsequent echoes slip through.

Fix Action

Workaround

Disable iMessage channel until patched.

PR fix notes

PR #61619: fix(imessage): avoid DM self-chat false positives

Description (problem / solution / changelog)

Summary

  • Problem: iMessage DM outbound rows can be misclassified as self-chat when imsg reports the peer handle in both sender and chat_identifier.
  • Why it matters: that sends normal DMs down the self-chat echo path, so a cache miss can leak the bot's own outbound reply back into inbound handling and start a loop.
  • What changed: the iMessage monitor now uses destination_caller_id when available to distinguish true self-chat from DM false positives, and it preserves that metadata in the parsed payload.
  • What did NOT change (scope boundary): no echo-cache redesign, no config changes, and no behavior change for older payloads that do not include destination_caller_id.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #61543
  • Related #59845
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: resolveIMessageInboundDecision() treated sender === chat_identifier as sufficient proof of self-chat, but current imsg payloads can use the peer handle for both fields in a normal DM outbound row.
  • Missing detection / guardrail: the monitor ignored destination_caller_id, which is the field that differentiates a real self-chat from this DM false positive.
  • Contributing context (if known): the earlier self-chat fixes addressed reflected duplicate rows, but they still depended on the older heuristic for identifying self-chat in the first place.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/imessage/src/monitor/self-chat-dedupe.test.ts, extensions/imessage/src/monitor.gating.test.ts
  • Scenario the test should lock in: is_from_me=true DM rows with sender===chat_identifier but a different destination_caller_id must still take the normal from me drop path, while real self-chat still dispatches.
  • Why this is the smallest reliable guardrail: the bug lives entirely inside inbound payload parsing and the inbound-decision branch, so focused tests on those two seams cover the real regression surface.
  • Existing test that already covers this (if any): the existing self-chat tests covered the legacy heuristic and true self-chat behavior, but not the DM false-positive shape.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • OpenClaw no longer treats normal iMessage DM outbound rows as self-chat when imsg provides a different destination_caller_id.
  • That keeps those rows on the normal from me drop path and avoids self-echo loops caused by cache misses.

Diagram (if applicable)

Before:
[normal DM outbound row]
  -> sender == chat_identifier
  -> misclassified as self-chat
  -> echo cache miss
  -> dispatched as inbound

After:
[normal DM outbound row]
  -> destination_caller_id != sender
  -> classified as normal DM
  -> dropped as from me

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
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS 15.3 / Darwin 25.3.0
  • Runtime/container: Node.js via repo scripts
  • Model/provider: N/A
  • Integration/channel: iMessage via imsg
  • Relevant config (redacted): standard iMessage channel config; no config changes required

Steps

  1. Receive a normal iMessage DM where imsg reports an outbound row with the peer handle in both sender and chat_identifier.
  2. Let OpenClaw process the is_from_me=true row.
  3. Observe whether it takes the self-chat path or the normal from me drop path.

Expected

  • The row is recognized as a normal DM outbound event and dropped as from me.
  • Real self-chat rows still use the self-chat path.

Actual

  • Before this change, the row could be misclassified as self-chat and leak through on an echo-cache miss.
  • After this change, the same shape drops as from me when destination_caller_id points at a different local handle.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: ran a direct node --import tsx repro against resolveIMessageInboundDecision() for the reported DM false-positive payload shape and for the true self-chat shape; the DM case now drops as from me and the true self-chat case still dispatches.
  • Edge cases checked: parser now preserves destination_caller_id; current fallback behavior remains unchanged when that field is absent.
  • What you did not verify: the heavier Vitest lanes (pnpm test, pnpm test:extension imessage, and a focused Vitest invocation) stalled after Vitest startup in this environment, so I did not get a completed test summary from those commands.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

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

Risks and Mitigations

  • Risk: older imsg payloads that omit destination_caller_id still rely on the legacy heuristic.

    • Mitigation: the new logic is additive and only tightens classification when the stronger signal is present, so older payload behavior is preserved rather than reinterpreted.
  • Risk: this assumes destination_caller_id is the local sending identity for the normal DM false-positive shape.

    • Mitigation: the new regression test locks in the exact reported payload shape, and the change is scoped to is_from_me self-chat classification only.

AI Disclosure

This PR was prepared with AI assistance and reviewed before submission.

Made with Cursor

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • extensions/imessage/src/monitor.gating.test.ts (modified, +16/-0)
  • extensions/imessage/src/monitor/inbound-processing.ts (modified, +16/-6)
  • extensions/imessage/src/monitor/parse-notification.ts (modified, +1/-0)
  • extensions/imessage/src/monitor/self-chat-dedupe.test.ts (modified, +82/-3)
  • extensions/imessage/src/monitor/types.ts (modified, +1/-0)

PR #62191: fix(imessage): strip U+FFFD garbage chars from echo text key normalization

Description (problem / solution / changelog)

Summary

U+FFFD replacement characters and C0/C1 control characters injected by imsg when extracting text from NSAttributedString (attributedBody column) break echo cache text-key matching, causing duplicate message delivery.

The echo cache stores clean outbound text, but the reflected inbound copy carries garbage character prefixes — normalizeEchoTextKey() only strips \r\n, so the keys never match and the echo slips through as a new inbound message.

Fix: Strip [\ufffd\ufffe\uffff\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]+ before comparison in normalizeEchoTextKey().

Previously included fixes (now upstream)

The original PR included two additional fixes that have since been merged independently by the OpenClaw team (#61619, #63868, #63980, #63989, #64000):

  • TTL 4s → 30s — shipped in 2026.4.10 (SENT_MESSAGE_TEXT_TTL_MS = 30e3)
  • destination_caller_id self-chat detection — shipped in 2026.4.10 with isSelfChat + isAmbiguousSelfThread

This PR now contains only the remaining unmerged fix.

Changes

  • extensions/imessage/src/monitor/echo-cache.ts: Strip U+FFFD/control chars in normalizeEchoTextKey

Related Issues

Fixes #61312, #61821

Test Plan

  • Confirmed echo cache miss on messages with U+FFFD prefixes (unpatched)
  • Applied patch to compiled dist — garbage-prefixed echoes now correctly matched and suppressed
  • Clean text (no U+FFFD) still matches normally — no regression

Changed files

  • extensions/imessage/src/monitor/echo-cache.ts (modified, +8/-1)

Code Example

const isSelfChat = !isGroup && chatIdentifier != null 
  && normalizeIMessageHandle(sender) === normalizeIMessageHandle(chatIdentifier);
RAW_BUFFERClick to expand / collapse

Bug

Version: 2026.4.1 (da64a97)
Channel: iMessage (via imsg CLI)

Description

Outbound iMessage DM replies echo back as new inbound messages, creating a feedback loop. The echo detection system exists but fails to catch these because the conversation is misclassified as a "self-chat."

Root Cause

In monitor-provider (~line 909), the self-chat detection logic:

const isSelfChat = !isGroup && chatIdentifier != null 
  && normalizeIMessageHandle(sender) === normalizeIMessageHandle(chatIdentifier);

In a normal DM, imsg reports sender as the peer handle (e.g. +1XXXXXXXXXX) regardless of message direction. The chatIdentifier for that DM is also the peer handle. So sender === chatIdentifier is always true, and isSelfChat is always true for all DMs.

This matters because when is_from_me: true + isSelfChat: true, the code enters the self-chat echo cache path (4s text TTL) instead of the normal is_from_me instant-drop path. Once the 4s cache expires, subsequent echoes slip through.

Expected Behavior

For a normal DM (not actually messaging yourself), is_from_me: true messages should be dropped immediately via the "from me" reason — the else return { kind: "drop", reason: "from me" } branch.

Suggested Fix

The isSelfChat check should also require that the chat is genuinely a self-chat (user messaging their own handle), not just that sender matches chatIdentifier. For example, comparing against the local account handle, or checking if destination_caller_id matches sender.

Reproduction

  1. Configure iMessage channel with dmPolicy: allowlist, allowFrom: ["+1XXXXXXXXXX"]
  2. Send any message from that number
  3. Agent replies via imsg send
  4. Outbound reply appears in imsg watch as is_from_me: true, sender: +1XXXXXXXXXX
  5. sender === chatIdentifier → classified as self-chat → echo cache used instead of instant drop
  6. After 4s TTL, echoes start looping

Workaround

Disable iMessage channel until patched.

extent analysis

TL;DR

Modify the isSelfChat check to require a genuine self-chat by comparing the sender against the local account handle or checking if destination_caller_id matches the sender.

Guidance

  • Review the monitor-provider code around line 909 to understand the current self-chat detection logic and its limitations.
  • Consider adding an additional check to verify if the conversation is a genuine self-chat, such as comparing the sender against the local account handle.
  • Test the modified isSelfChat check with different scenarios, including normal DMs and self-chats, to ensure it correctly classifies conversations.
  • As a temporary workaround, consider disabling the iMessage channel until a patch is available to prevent the echo loop issue.

Example

const isSelfChat = !isGroup && chatIdentifier != null 
  && normalizeIMessageHandle(sender) === normalizeIMessageHandle(chatIdentifier)
  && normalizeIMessageHandle(sender) === normalizeIMessageHandle(localAccountHandle);

This example adds an additional check to compare the sender against the local account handle, which can help identify genuine self-chats.

Notes

The provided fix is based on the assumption that the localAccountHandle is available and can be used to compare against the sender. If this is not the case, an alternative approach may be needed.

Recommendation

Apply workaround: Disable the iMessage channel until a patch is available to prevent the echo loop issue, as the modified isSelfChat check may require additional testing and verification to ensure its correctness.

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…

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 iMessage echo loop: DM incorrectly classified as self-chat, bypassing echo filter [2 pull requests, 1 comments, 2 participants]