Skip to content

fix(imessage): detect and recover anchorless watch payloads before routing#84503

Closed
zhangguiping-xydt wants to merge 6 commits into
openclaw:mainfrom
zhangguiping-xydt:feat/issue-84470
Closed

fix(imessage): detect and recover anchorless watch payloads before routing#84503
zhangguiping-xydt wants to merge 6 commits into
openclaw:mainfrom
zhangguiping-xydt:feat/issue-84470

Conversation

@zhangguiping-xydt

@zhangguiping-xydt zhangguiping-xydt commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix iMessage group link-preview watch payloads that lose their conversation anchor (chat_id=0, empty chat_guid / chat_identifier, is_group=false) before they can be classified as sender DMs.
  • Move anchorless recovery/fail-closed drop ahead of live inbound debounce classification so coalesceSameSenderDms cannot merge malformed group payloads under the raw chat:0 DM bucket.
  • Keep the existing recovery path for catchup/direct handling and add monitor-level regression coverage for the live coalescing path.

Real behavior proof

Behavior or issue addressed: iMessage group link-preview watch payloads can arrive without usable conversation anchor fields. Before this fix, live notifications entered the inbound debouncer first, so with coalesceSameSenderDms=true multiple anchorless group payloads from the same sender could be merged under imessage:<account>:dm:chat:0:<sender> before recovery. The merged turn was then repaired using only the first GUID and could route content from another group under the first recovered group.

Real environment tested: macOS 26.4.1, Node.js v24.15.0, OpenClaw v2026.5.19, worktree SHA e22876fd1bef, imsg 0.9.0 installed at /opt/homebrew/bin/imsg.

Exact steps or command run after this patch:

imsg status --json
printf '{"jsonrpc":"2.0","id":1,"method":"chats.list","params":{"limit":3}}\n' | imsg rpc --json

OPENCLAW_GATEWAY_PORT=19046 \
OPENCLAW_GATEWAY_TOKEN=pr-84503-local-token \
OPENCLAW_STATE_DIR=/Users/zhangguiping/daily_pr_work/proofs/openclaw-pr-84503-local-proof/state \
OPENCLAW_CONFIG_PATH=/Users/zhangguiping/daily_pr_work/proofs/openclaw-pr-84503-local-proof/state/openclaw.json \
node scripts/run-node.mjs --dev gateway --bind custom --port 19046

curl --noproxy '*' -sS -I http://127.0.0.1:19046/
curl --noproxy '*' -sS http://127.0.0.1:19046/health

OPENCLAW_GATEWAY_PORT=19046 \
OPENCLAW_GATEWAY_TOKEN=pr-84503-local-token \
OPENCLAW_STATE_DIR=/Users/zhangguiping/daily_pr_work/proofs/openclaw-pr-84503-local-proof/state \
OPENCLAW_CONFIG_PATH=/Users/zhangguiping/daily_pr_work/proofs/openclaw-pr-84503-local-proof/state/openclaw.json \
node scripts/run-node.mjs channels status --probe --channel imessage --json

pnpm exec vitest run \
  extensions/imessage/src/monitor.watch-subscribe-retry.test.ts \
  extensions/imessage/src/monitor/conversation-repair.test.ts \
  extensions/imessage/src/monitor.gating.test.ts \
  extensions/imessage/src/monitor/coalesce.test.ts

Evidence after fix:

$ imsg status --json
{
  "version": "0.9.0",
  "basic_features": true,
  "sip": "enabled",
  "advanced_features": false,
  "rpc_methods": [
    "chats.list",
    "messages.history",
    "watch.subscribe",
    "watch.unsubscribe",
    "send",
    "send.rich",
    "send.attachment"
  ]
}

$ printf '{"jsonrpc":"2.0","id":1,"method":"chats.list","params":{"limit":3}}\n' | imsg rpc --json
{"result":{"chats":[]},"id":1,"jsonrpc":"2.0"}
2026-05-24T18:23:21.205+08:00 [gateway] http server listening (10 plugins: acpx, bonjour, browser, canvas, device-pair, file-transfer, imessage, memory-core, phone-control, talk-voice; 1.7s)
2026-05-24T18:23:21.211+08:00 [gateway] starting channels and sidecars...
2026-05-24T18:23:21.264+08:00 [imessage] [default] starting provider (/opt/homebrew/bin/imsg db=/Users/zhangguiping/Library/Messages/chat.db)
2026-05-24T18:23:21.646+08:00 [gateway] ready
$ curl --noproxy '*' -sS -I http://127.0.0.1:19046/
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

$ curl --noproxy '*' -sS http://127.0.0.1:19046/health
{"ok":true,"status":"live"}
$ node scripts/run-node.mjs channels status --probe --channel imessage --json
{
  "channels": {
    "imessage": {
      "configured": true,
      "cliPath": "/opt/homebrew/bin/imsg",
      "dbPath": "/Users/zhangguiping/Library/Messages/chat.db",
      "running": true,
      "lastError": null,
      "probe": {
        "ok": true,
        "privateApi": {
          "available": false,
          "rpcMethods": [
            "chats.list",
            "messages.history",
            "watch.subscribe",
            "watch.unsubscribe"
          ]
        }
      }
    }
  },
  "channelAccounts": {
    "imessage": [
      {
        "accountId": "default",
        "enabled": true,
        "configured": true,
        "running": true,
        "lastError": null,
        "probe": {
          "ok": true
        }
      }
    ]
  }
}
Test Files  4 passed (4)
Tests       51 passed (51)
Duration    2.82s

Observed result after fix: The patched PR source starts a real OpenClaw gateway with the real iMessage plugin enabled, not skipped. The gateway starts /opt/homebrew/bin/imsg against the local Messages database, reaches ready, reports the iMessage account as configured/running with lastError: null, and the live channel probe confirms the watch.subscribe, messages.history, and chats.list RPC methods required by the anchorless-payload recovery path. The regression test for the reported live coalescing path still verifies that two anchorless payloads from different groups are repaired before debounce classification, so they no longer share the raw chat:0 DM bucket.

Additional live evidence gathered after this patch:

  • A real iMessage group chat exists locally (chat_id=3, is_group=true), and imsg messages.history shows a live URL message in that group:
    • https://github.com/openclaw/openclaw/pull/84503
  • imsg watch.subscribe was started live and returned a real notification for a new group URL message sent with the same account:
    • subscription=1
    • chat_id=3
    • is_group=true
    • text=https://github.com/openclaw/openclaw/pull/84503
  • That proves the local Messages.app + imsg + OpenClaw watch pipeline is live end-to-end on a real group conversation, but the notification payload itself was not malformed.

What was not tested: No new malformed group link-preview notification arrived from the local Messages account during this run, so the evidence does not include a live incoming private message/GUID. The live proof above verifies the patched OpenClaw runtime, real imsg bridge, local Messages DB access, and iMessage watch-capable channel startup; the exact malformed payload branch is covered by the monitor-level regression test using redacted synthetic GUIDs and handles.

Fixes #84470

Regression Test Plan

  • Coverage level: Unit test
  • Target test file: extensions/imessage/src/monitor.watch-subscribe-retry.test.ts, extensions/imessage/src/monitor/conversation-repair.test.ts, extensions/imessage/src/monitor.gating.test.ts, extensions/imessage/src/monitor/coalesce.test.ts
  • Scenario locked in: Anchorless live iMessage payloads are repaired before inbound debounce classification, unrecoverable anchorless payloads fail closed, and existing conversation-repair/gating/coalescing behavior remains covered.
  • Why this is the smallest reliable guardrail: The regression drives the same monitor/watch path where live watch.subscribe notifications enter OpenClaw, while keeping private iMessage handles and GUIDs out of the repository.

Root Cause

  • Root cause: Live iMessage watch payloads were entering the inbound debouncer before conversation-anchor recovery. When a malformed group payload looked like chat_id=0, empty chat identifiers, and is_group=false, coalesceSameSenderDms could classify it as a sender DM before the GUID-based repair path had a chance to recover the real group.
  • Missing detection / guardrail: The live monitor path lacked a pre-debounce guard for anchorless payloads and lacked regression coverage proving anchorless group payloads cannot be coalesced under a raw DM bucket.

@openclaw-barnacle openclaw-barnacle Bot added channel: imessage Channel integration: imessage size: L triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 20, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as superseded: #86150 now owns the same iMessage anchorless-payload repair on current main, with broader detection, route-level assertions, changelog coverage, and explicit credit for this contribution.

Canonical path: Use #86150 as the canonical landing path for #84470, porting any uniquely useful assertion from this branch only if maintainers want it.

So I’m closing this here and keeping the remaining discussion on #86150 and #84470.

Review details

Best possible solution:

Use #86150 as the canonical landing path for #84470, porting any uniquely useful assertion from this branch only if maintainers want it.

Do we have a high-confidence way to reproduce the issue?

Yes at source level: current main enqueues the reported chat_id=0, empty identifier, is_group=false shape before repair, where coalesceSameSenderDms can bucket it as a sender DM. I did not reproduce a fresh live malformed iMessage notification in this checkout.

Is this the best way to solve the issue?

No for this branch as the landing vehicle: the repair direction is valid, but the newer canonical PR carries the same fix on current main with broader detection, route-level proof, and changelog coverage. The best solution is to land or close that canonical PR, not merge both.

Security review:

Security review cleared: No dependency, CI, secret-handling, or supply-chain regression was found; the privacy-sensitive routing behavior is covered by merge-risk and superseded-branch handling.

What I checked:

Likely related people:

  • omarshahine: Assigned to this PR and opened the newer maintainer-labeled replacement that explicitly carries the same fix path, closes the linked bug, references this PR, and credits the contributor work. (role: canonical follow-up owner; confidence: high; commits: ff1d9dac2ebb; files: extensions/imessage/src/monitor/conversation-repair.ts, extensions/imessage/src/monitor/monitor-provider.ts, extensions/imessage/src/monitor.last-route.test.ts)
  • steipete: git blame on the central coalesceSameSenderDms block points to Peter Steinberger's commit that added the current monitor-provider and coalescing implementation in this checkout. (role: introduced current debounce/coalescing path; confidence: high; commits: 04d86e0f4721; files: extensions/imessage/src/monitor/monitor-provider.ts, extensions/imessage/src/monitor/coalesce.ts, extensions/imessage/src/monitor.watch-subscribe-retry.test.ts)
  • Andy Ye: Recent iMessage monitor work adjusted live catchup cursor handling around the same watch/monitor surface, making this person a useful routing candidate for adjacent behavior questions. (role: recent adjacent contributor; confidence: medium; commits: 102555c6e027; files: extensions/imessage/src/monitor/monitor-provider.ts, extensions/imessage/src/monitor/catchup-bridge.ts, extensions/imessage/src/monitor.last-route.test.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against ad71a998ff94.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 20, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 20, 2026
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. label May 22, 2026
@omarshahine omarshahine self-assigned this May 24, 2026
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: supplied External PR includes structured after-fix real behavior proof. and removed proof: supplied External PR includes structured after-fix real behavior proof. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 24, 2026
@zhangguiping-xydt

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels May 24, 2026
@zhangguiping-xydt

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

Re-review progress:

…uting

When watch.subscribe delivers a group link-preview payload with chat_id=0,
empty chat_guid/chat_identifier, and is_group=false, the message loses its
conversation anchor and was being routed to the sender DM instead of the
group. Add an anchorless detection guard that recovers the real conversation
via chats.list + messages.history RPC lookup, or drops the event fail-closed
when recovery is not possible.
The conversation-repair integration passed `repaired` to
resolveIMessageInboundDecision but continued using the original
`message` for rate limiting, allowlist warnings, and context
building. This meant repaired chat_id/chat_name/chat_guid were
lost after the routing decision, causing wrong rate-limit keys
and stale metadata in the agent context payload.
Narrow anchorless recovery to explicit empty conversation fields so direct messages that rely on sender routing pass through unchanged.
Move anchor recovery ahead of live debounce classification so malformed group payloads cannot be coalesced as sender DMs before routing.
@zhangguiping-xydt

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper

clawsweeper Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this May 24, 2026
omarshahine added a commit that referenced this pull request May 25, 2026
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes #84470.
Refs #84503.

Thanks @zhangguiping-xydt and @zqchris.
@omarshahine

Copy link
Copy Markdown
Contributor

Superseded by the maintainer fix in #86150, merged as f37fbc9.

Thanks @zhangguiping-xydt for the contributor repair direction here. The landed fix keeps the same goal, reapplies it on current main, adds broader anchorless detection, route-level regression coverage, and real Lobster Mac proof against the live iMessage bridge.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 25, 2026
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes openclaw#84470.
Refs openclaw#84503.

Thanks @zhangguiping-xydt and @zqchris.
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes openclaw#84470.
Refs openclaw#84503.

Thanks @zhangguiping-xydt and @zqchris.
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes openclaw#84470.
Refs openclaw#84503.

Thanks @zhangguiping-xydt and @zqchris.
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes openclaw#84470.
Refs openclaw#84503.

Thanks @zhangguiping-xydt and @zqchris.
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes openclaw#84470.
Refs openclaw#84503.

Thanks @zhangguiping-xydt and @zqchris.
SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes openclaw#84470.
Refs openclaw#84503.

Thanks @zhangguiping-xydt and @zqchris.
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes openclaw#84470.
Refs openclaw#84503.

Thanks @zhangguiping-xydt and @zqchris.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: imessage Channel integration: imessage merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P1 High-priority user-facing bug, regression, or broken workflow. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: L status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iMessage group link-preview watch payload can lose conversation anchor and route reply to DM

2 participants