openclaw - ✅(Solved) Fix [Bug]: iMessage echo loop on gateway restart — echo cache not warmed, self-messages replay as inbound [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

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#61445Fetched 2026-04-08 02:58:33
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1referenced ×1

After a gateway restart, the in-memory echo cache is empty. iMessage outbound replies written to the Messages DB with is_from_me=True and sender=<bot_number> are not matched by the echo filter, so they slip through as new inbound messages — triggering a reply loop.

Root Cause

The echo cache is in-memory and cleared on gateway restart. The iMessage DB monitor has a race window at startup where:

  • The cache is empty
  • Previously-written is_from_me=True messages (or new outbound replies written immediately post-restart) have no cache match
  • Those messages pass the echo filter and are processed as inbound

The loop is self-healing: once the cache warms up (next outbound message is cached before the DB write), the loop stops. In this case, a NO_REPLY response (no DB write) broke the loop.

PR fix notes

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

Summary

After a gateway restart, the in-memory echo cache is empty. iMessage outbound replies written to the Messages DB with is_from_me=True and sender=<bot_number> are not matched by the echo filter, so they slip through as new inbound messages — triggering a reply loop.

Steps to Reproduce

  1. Have the iMessage channel configured with a number that matches the bot's own phone number (self-chat detection path)
  2. Restart the OpenClaw gateway
  3. Send any message that triggers an agent reply
  4. Observe: agent replies → iMessage DB write → monitor fires → reply treated as new inbound → agent replies again → loop

Root Cause

The echo cache is in-memory and cleared on gateway restart. The iMessage DB monitor has a race window at startup where:

  • The cache is empty
  • Previously-written is_from_me=True messages (or new outbound replies written immediately post-restart) have no cache match
  • Those messages pass the echo filter and are processed as inbound

The loop is self-healing: once the cache warms up (next outbound message is cached before the DB write), the loop stops. In this case, a NO_REPLY response (no DB write) broke the loop.

Suggested Fix

Option 1 — Startup rowid fence (low-friction):
At startup, record max_rowid from the Messages DB. During a configurable grace window (e.g., first 30s after start), skip any is_from_me=True messages with rowid <= startup_max_rowid. This prevents already-written outbound messages from replaying without requiring cache persistence.

Option 2 — Persist echo cache to disk:
Serialize the echo cache (message GUIDs / rowids) to a small file on write, reload on startup. Heavier but fully closes the race.

Option 3 — Always filter is_from_me=True during startup grace window:
For N seconds post-restart, treat all is_from_me=True messages as cache hits regardless of cache state.

Option 1 (rowid fence) is the lowest-risk change with the clearest correctness boundary.

Environment

  • Channel: iMessage
  • Gateway restart trigger: normal process restart (not crash)
  • Self-chat detection: bot phone number == chat handle number

Community

Discussion in OpenClaw Discord

extent analysis

TL;DR

Implement a startup rowid fence to prevent replaying already-written outbound messages during the gateway's startup grace window.

Guidance

  • Consider Option 1 — Startup rowid fence: record max_rowid from the Messages DB at startup and skip is_from_me=True messages with rowid <= startup_max_rowid during a configurable grace window.
  • Evaluate the trade-offs between the suggested fix options: Option 1 (low-friction), Option 2 (persist echo cache to disk), and Option 3 (always filter is_from_me=True during startup).
  • Assess the feasibility of implementing a cache persistence mechanism, such as serializing the echo cache to a file, to fully close the race window.
  • Review the community discussion in the OpenClaw Discord for additional insights and potential solutions.

Example

No code snippet is provided, as the issue suggests various approaches without specifying a particular implementation.

Notes

The choice of fix depends on the specific requirements and constraints of the system, including the acceptable level of complexity and potential performance impact.

Recommendation

Apply Option 1 — Startup rowid fence as the lowest-risk change with a clear correctness boundary, as it provides a simple and effective solution to prevent the reply loop during the gateway's startup.

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