Version
OpenClaw 2026.4.2 (d74a122)
macOS 26.3.1 (arm64), imsg via Homebrew
Summary
The iMessage plugin's isSelfChat heuristic false-positives on all DM chats, causing outbound messages to sometimes bypass the "from me" drop filter and leak back as inbound messages. This creates an infinite self-echo loop.
Reproduction
- Have an iMessage DM chat with contact
+1234567890
- The chat's
identifier in Messages.app DB is +1234567890
- Send a reply via OpenClaw
imsg watch picks up the new DB row with is_from_me: true, sender: "+1234567890"
- Plugin evaluates
normalizeIMessageHandle(sender) === normalizeIMessageHandle(chatIdentifier) → true
- Instead of
return { kind: "drop", reason: "from me" }, enters echo-cache path
- Echo cache misses (encoding artifacts from Messages.app attributed string storage corrupt the text)
- Message leaks through as inbound → generates reply → infinite loop
Root Cause
In monitor-provider-BaCJr_L0.js around line 917:
if (params.message.is_from_me) {
params.selfChatCache?.remember(selfChatLookup);
if (isSelfChat) {
// Echo cache detection (designed for "Notes to Self" chats)
// FALSE POSITIVE: in a DM, chatIdentifier IS the peer's handle,
// and imsg reports sender as the peer's handle for outbound too
} else {
return { kind: "drop", reason: "from me" }; // correct path, never reached in DMs
}
}
The isSelfChat check:
const isSelfChat = !isGroup && chatIdentifier != null
&& normalizeIMessageHandle(sender) === normalizeIMessageHandle(chatIdentifier);
This is always true for DM outbound messages because imsg reports sender = peer handle = chatIdentifier.
Echo Cache Failure Mode
The echo cache compares exact text, but round-tripped messages have:
- Binary encoding artifacts (attributed string metadata bytes decoded as UTF-8)
- Possible text chunking differences
- The garbled prefixes (
\xef\xbf\xbd, \x01, etc.) guarantee no match
Observed Impact
- ~20+ rapid-fire self-echo messages in a single DM
- Session transcript polluted with 295 lines of echo garbage
- Required manual session reset + gateway restart
Suggested Fixes
- Simplest: For
is_from_me=true in non-group, non-actual-self chats, always drop. Detect actual self-chat by comparing against the Mac's own registered handles rather than chatIdentifier.
- Alternative: Use message GUID tracking for echo detection instead of text comparison.
- Defense in depth: Add a rate limiter — if >N replies to the same chat in <M seconds, pause and flag.
Workaround
Reset the poisoned session transcript (archive the .jsonl, keep header line only, restart gateway).
The echo loop stops once the accumulated garbage is cleared, but can recur on any DM.
Version
OpenClaw 2026.4.2 (d74a122)
macOS 26.3.1 (arm64),
imsgvia HomebrewSummary
The iMessage plugin's
isSelfChatheuristic false-positives on all DM chats, causing outbound messages to sometimes bypass the"from me"drop filter and leak back as inbound messages. This creates an infinite self-echo loop.Reproduction
+1234567890identifierin Messages.app DB is+1234567890imsg watchpicks up the new DB row withis_from_me: true,sender: "+1234567890"normalizeIMessageHandle(sender) === normalizeIMessageHandle(chatIdentifier)→ truereturn { kind: "drop", reason: "from me" }, enters echo-cache pathRoot Cause
In
monitor-provider-BaCJr_L0.jsaround line 917:The
isSelfChatcheck:This is always true for DM outbound messages because
imsgreportssender= peer handle =chatIdentifier.Echo Cache Failure Mode
The echo cache compares exact text, but round-tripped messages have:
\xef\xbf\xbd,\x01, etc.) guarantee no matchObserved Impact
Suggested Fixes
is_from_me=truein non-group, non-actual-self chats, always drop. Detect actual self-chat by comparing against the Mac's own registered handles rather thanchatIdentifier.Workaround
Reset the poisoned session transcript (archive the
.jsonl, keep header line only, restart gateway).The echo loop stops once the accumulated garbage is cleared, but can recur on any DM.