openclaw - 💡(How to fix) Fix iMessage catch-up can replay live-handled messages after restart when cursor lags

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…

iMessage catch-up can replay already-processed inbound messages after a gateway restart even when channels.imessage.catchup.enabled is enabled and the catch-up cursor exists on disk.

This appears to happen because live iMessage handling does not advance the persisted catch-up cursor, while the inbound dedupe cache used to absorb overlap is process-local memory. After a restart, the in-memory dedupe cache is empty, so catch-up can re-dispatch recent inbound rows that were already handled live before the restart.

This is related to #62761, but it is a narrower follow-up for the newer opt-in catch-up path: cursor persistence exists, but the cursor can still lag behind live-handled rows.

Root Cause

This appears to happen because live iMessage handling does not advance the persisted catch-up cursor, while the inbound dedupe cache used to absorb overlap is process-local memory. After a restart, the in-memory dedupe cache is empty, so catch-up can re-dispatch recent inbound rows that were already handled live before the restart.

Fix Action

Fix / Workaround

This appears to happen because live iMessage handling does not advance the persisted catch-up cursor, while the inbound dedupe cache used to absorb overlap is process-local memory. After a restart, the in-memory dedupe cache is empty, so catch-up can re-dispatch recent inbound rows that were already handled live before the restart.

Because the inbound dedupe cache is process-local and has been reset by the restart, those already-processed inbound rows were treated as fresh messages and dispatched again, producing duplicate agent replies.

Restarting the gateway should not cause recent iMessage rows that were already processed by the live watcher to be dispatched again.

Code Example

[imessage] catchup: replayed=2 skippedFromMe=2 skippedGivenUp=0 failed=0 givenUp=0 fetchedCount=4
RAW_BUFFERClick to expand / collapse

Summary

iMessage catch-up can replay already-processed inbound messages after a gateway restart even when channels.imessage.catchup.enabled is enabled and the catch-up cursor exists on disk.

This appears to happen because live iMessage handling does not advance the persisted catch-up cursor, while the inbound dedupe cache used to absorb overlap is process-local memory. After a restart, the in-memory dedupe cache is empty, so catch-up can re-dispatch recent inbound rows that were already handled live before the restart.

This is related to #62761, but it is a narrower follow-up for the newer opt-in catch-up path: cursor persistence exists, but the cursor can still lag behind live-handled rows.

Environment

  • OpenClaw: 2026.5.20 (e510042)
  • imsg: 0.9.0
  • macOS: 26.3 arm64
  • iMessage catch-up: enabled

What happened

After a gateway restart, the iMessage provider started cleanly and catch-up ran. The catch-up log showed recent rows being replayed:

[imessage] catchup: replayed=2 skippedFromMe=2 skippedGivenUp=0 failed=0 givenUp=0 fetchedCount=4

At that point, the persisted catch-up cursor was behind the current Messages database high-water row by several rows. The rows after the cursor included messages that had already been handled by the live watcher before/around the restart.

Because the inbound dedupe cache is process-local and has been reset by the restart, those already-processed inbound rows were treated as fresh messages and dispatched again, producing duplicate agent replies.

Expected behavior

Restarting the gateway should not cause recent iMessage rows that were already processed by the live watcher to be dispatched again.

The persisted catch-up state should be sufficient to prevent duplicate inbound dispatch across restarts, or there should be a persistent provider-message dedupe keyed by stable iMessage identifiers.

Actual behavior

The catch-up cursor can remain behind live-handled traffic. On restart, catch-up queries from the stale cursor, and the process-local inbound dedupe cache no longer remembers the earlier live dispatches.

The resulting user-visible behavior is duplicate replies after restart.

Likely cause

The catch-up path calls performIMessageCatchup, which persists lastSeenMs / lastSeenRowid after a catch-up pass. However, live inbound handling via watch.subscribe appears not to advance that same persisted cursor when messages are successfully handled, skipped as from me, or otherwise resolved.

Separately, runIMessageCatchup relies on the normal inbound dedupe cache to absorb overlap. That cache is process-local with a TTL, so it does not protect restart boundaries.

Suggested fix

One of these should make the behavior restart-safe:

  • Advance the iMessage catch-up cursor monotonically from the live inbound path after a row is handled or safely skipped.
  • Add a persistent inbound dedupe store keyed by stable iMessage message GUID/rowid and route scope.
  • Add a startup high-water fence so catch-up cannot replay rows that were already observed by the previous live watcher.

The cursor update should be monotonic/merge-safe so an older catch-up pass cannot regress the high-water mark.

Privacy note

This report intentionally omits personal message contents, handles, account aliases, chat identifiers, message GUIDs, and local state paths. The repro details above are sanitized to the channel behavior and log shape only.

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

Restarting the gateway should not cause recent iMessage rows that were already processed by the live watcher to be dispatched again.

The persisted catch-up state should be sufficient to prevent duplicate inbound dispatch across restarts, or there should be a persistent provider-message dedupe keyed by stable iMessage identifiers.

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 - 💡(How to fix) Fix iMessage catch-up can replay live-handled messages after restart when cursor lags