fix(imessage): gate split-send coalescing on imsg metadata#90858
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 7, 2026, 9:45 PM ET / 01:45 UTC. Summary PR surface: Source +76, Tests +223, Docs +3. Total +302 across 8 files. Reproducibility: yes. source inspection gives a high-confidence reproduction path: current main combines every multi-entry debounced iMessage DM bucket unconditionally when Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: A maintainer should explicitly accept the interim compatibility tradeoff and land this only if OpenClaw wants client-side precision before upstream Do we have a high-confidence way to reproduce the issue? Yes, source inspection gives a high-confidence reproduction path: current main combines every multi-entry debounced iMessage DM bucket unconditionally when Is this the best way to solve the issue? Yes, this is an acceptable interim fix: it moves the merge decision from text/timing inference to AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against b16a43597d49. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +76, Tests +223, Docs +3. Total +302 across 8 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
Real behavior proofBehavior addressed: iMessage split-send coalescing is now gated on the structural Real environment tested: lobster (macOS 26.4.1, Apple Silicon), against the real Messages Exact steps / commands run after this patch: # 1) The metadata input (imsg#137) on real chat.db
imsg history --chat-id 3 --limit 40 --json | grep -o 'balloon_bundle_id[^,]*' | sort | uniq -c
# 2) This PR's coalescing gate logic
node scripts/run-vitest.mjs extensions/imessage/src/monitor/coalesce.test.tsEvidence after fix:
Observed result after fix: The exact structural signal this PR keys on ( What was not tested: A full live gateway coalescing of a freshly received split-send. That requires running this branch's gateway with the imsg#137 build end-to-end; deploying a newer-main build to the production gateway host hit packaging/config-migration risk, so it was deferred to a disposable VM rather than forced on production. The gate logic + the real metadata input are proven; the only unproven piece is the runtime wiring between them, which |
db314a7 to
d454fa3
Compare
|
@clawsweeper re-review Two updates since the prior review:
Dependency state: openclaw/imsg#137 ( |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
…th back-compat Use a session-level balloon-capability latch (not a per-bucket absence check) to decide whether to merge a debounced same-sender DM bucket: - URL-balloon marker present -> merge (precise split-send). - build known to emit balloon metadata -> keep non-marker buckets separate. - build never emitted balloon metadata -> preserve the legacy merge so older imsg builds (no balloon_bundle_id) do not regress Dump <url> split-sends. Never merges more than shipped main already did. Removed once imsg coalesces split-sends upstream (openclaw/imsg#141, tracked by openclaw#91243).
|
@clawsweeper re-review Material update since the last review: added a capability-gated back-compat path so this can ship ahead of the imsg #137 release without regressing existing
PR body updated with the full behavior + proof. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
…g builds The Flag-on column assumed the strict gate; on older imsg builds with no balloon metadata the back-compat path legacy-merges fieldless buckets. Note this on the table header and the attachment/flood rows.
Real behavior proof —
|
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review Proof comment above corrected after rebuilding imsg #137 (e223709): the marker surfaces correctly on a real split-send pair in the real chat.db; the earlier empty-text-filter note was a wrong-branch-build artifact. Head unchanged at 11c4454. |
|
🦞👀 Command router queued. I will update this comment with the next step. Re-review progress:
|
Live end-to-end proof — patched OpenClaw gateway coalescing a real split-send via imsg #137Ran the actual OpenClaw gateway from this PR's head ( Real inbound split-send (raw
Patched gateway log — the merge: ( This is the live patched monitor doing exactly what #90858 implements, end-to-end on real Apple data:
Combined with the prior real- @clawsweeper re-review |
|
🦞👀 Command router queued. I will update this comment with the next step. Re-review progress:
|
|
@clawsweeper re-review Prior re-reviews were superseded by concurrency. Re-requesting once: the end-to-end gap is now closed by the live capture above (#90858 (comment)) — the patched PR gateway, running live against a real imsg #137 build, merged a real iPhone-sent Dump-url split-send into one turn ( |
|
🦞👀 Command router queued. I will update this comment with the next step. Re-review progress:
|
…th back-compat (openclaw#90858) Gate iMessage same-sender DM split-send coalescing on imsg's structural `balloon_bundle_id` URL-balloon marker (openclaw/imsg#137) instead of timing/ text-shape inference, with a session capability latch and a back-compat path: - URL-balloon marker present -> merge (precise split-send). - Build known to emit balloon metadata (session latch) -> keep non-marker buckets separate (the precision win). - Build that never emits balloon metadata (older imsg) -> preserve the legacy unconditional merge, so split-send users do not regress to two turns. Never merges more than shipped main already did. Verified live end-to-end: the patched gateway, watching a real chat.db via an imsg openclaw#137 build, merged a real iPhone-sent `Dump <url>` split-send into one turn. Client-side removal once imsg coalesces upstream is tracked in openclaw#91243 (openclaw/imsg#141). Closes openclaw#90795
Summary
What problem does this PR solve?
imsgstructural URL-balloon metadata from feat: expose message balloon bundle metadata imsg#137.balloon_bundle_idat the OpenClaw iMessage RPC payload boundary.balloon_bundle_id: "com.apple.messages.URLBalloonProvider".coalesceSameSenderDmsusers on a releasedimsg(no balloon metadata yet) are not regressed while the dependency rolls out.How the merge decision works (capability-gated, three-way)
balloon_bundle_id): a bucket with no URL marker is genuinely not a split-send, so keep the rows as separate turns. This is the precision the structural gate exists for.imsg): we cannot structurally tell aDump <url>split-send from separate sends, so preserve the pre-metadata merge rather than regress split-send users to two turns.Why a session-level latch instead of a per-bucket check?
imsgomitsballoon_bundle_idon the wire for non-balloon rows, so a bucket of plain text rows looks identical on old and new builds. Absence of metadata in one bucket does not prove the build lacks the field. The capability signal therefore has to be session-scoped (monitor-provider.ts), not inferred per-bucket. (Caught by autoreview — see below.)Important invariant: this never merges more than the shipped behavior already does. With
coalesceSameSenderDmsenabled,maindebounces every same-sender DM and merges each multi-entry bucket unconditionally. An unlatched session (older build, or a metadata-capable build before its first balloon row) is therefore identical to today, not a new regression; once capability is observed it merges strictly less (more precise).Why does this matter now?
imsgPR gives OpenClaw a structural signal instead — and the back-compat path lets this ship ahead of theimsgrelease without regressing anyone.What is intentionally out of scope?
imsgfield only proves URL-preview balloon rows.imsg-advertised capability flag, which is part of the upstream coalescing work (Coalesce Apple URL-preview split-sends into a single notification imsg#141).What does success look like?
What should reviewers focus on?
monitor-provider.tshandleMessageingress) and whether the three-way gate incoalesce.tscorrectly preserves shipped behavior for metadata-less builds.Linked context
Which issue does this close?
Closes #90795
Which issues, PRs, or discussions are related?
balloon_bundle_idmetadata this consumes).imsgcoalesces upstream, including theopenclaw doctor --fixmigration to dropcoalesceSameSenderDms.Was this requested by a maintainer or owner?
Requested by maintainer in chat after closing #90795 in favor of the
imsgcontract fix.Real behavior proof (required for external PRs)
imsgballoon_bundle_idmetadata instead of short-message/string-shape inference, with a capability-gated back-compat path that preserves shipped merge behavior on metadata-lessimsgbuilds.11c4454ba3) ran on a real macOS 26.4.1 host, watching the real Messageschat.dbthrough a real imsg Gateway: avoid duplicate block stream replies #137 build (fix/expose-imessage-balloon-metadata,e223709) that emitsballoon_bundle_id. A realDump <url>split-send sent from an iPhone (Messages UI) was merged into one agent turn.imsgJSON-RPC notifications; siblingimsgsource inspected directly for the output-field contract (balloon_bundle_idisString?, omitted on the wire when nil).node scripts/run-vitest.mjs extensions/imessage/src/monitor/coalesce.test.ts extensions/imessage/src/monitor/parse-notification.test.ts extensions/imessage/src/monitor.last-route.test.tsnode scripts/run-oxlint.mjs extensions/imessage/src/monitor/coalesce.ts extensions/imessage/src/monitor/monitor-provider.ts extensions/imessage/src/monitor/coalesce.test.tsnpx oxfmt --check --threads=1 <changed .ts files>pnpm tsgo:extensions && pnpm tsgo:extensions:test.agents/skills/autoreview/scripts/autoreview --mode localtsgo:extensionsandtsgo:extensions:testclean.text="Dump"no marker, then the URL withballoon_bundle_id="com.apple.messages.URLBalloonProvider"1s later) → patched gateway logged[PROOF] MERGED bucket size=2 markers=1 buildCapable=true text="Dump https://www.theverge.com/news/..."— debounced into one bucket, saw the marker, latch flipped, merged into one turn. ([PROOF]was a temporary unconditional log added only on the throwaway host checkout, reverted; host restored to production.) See the live-proof comment.Dump+ URL bucket (metadata-less build) dispatches as one legacy-merged turn — matches shippedmain, no regression.balloon_bundle_id: "com.apple.messages.URLBalloonProvider"dispatches as one merged turn.imsgcontaining feat: expose message balloon bundle metadata imsg#137 (still pre-release) — but the live capture above used a build with the exact Gateway: avoid duplicate block stream replies #137 read-path, so runtime behavior is identical; on metadata-less releasedimsgthe back-compat path preserves shippedmain(no regression).imsg.Tests and validation
Which commands did you run?
node scripts/run-vitest.mjs extensions/imessage/src/monitor/coalesce.test.ts extensions/imessage/src/monitor/parse-notification.test.ts extensions/imessage/src/monitor.last-route.test.tsnode scripts/run-oxlint.mjs <changed monitor .ts files>npx oxfmt --check --threads=1 <changed .ts files>pnpm tsgo:extensions && pnpm tsgo:extensions:test.agents/skills/autoreview/scripts/autoreview --mode localWhat regression coverage was added or updated?
balloon_bundle_id.Which bot or reviewer comments were addressed?
mainbehavior (verifiedmainmerges every multi-entry bucket unconditionally), and the suggested alternative would re-break old-imsgsplit-sends; fully closing it needs theimsgcapability flag scoped into fix: complete gateway server refactoring and fix Swift compiler crash #141.Risk checklist
Did user-visible behavior change? (
Yes/No)Yes. On metadata-capable
imsgbuilds, opt-in split-send coalescing becomes precise (only real URL split-sends merge). On metadata-less builds, behavior is unchanged from shippedmain.Did config, environment, or migration behavior change? (
Yes/No)No config shape or migration change. Existing
channels.imessage.coalesceSameSenderDmsremains the switch. (Its eventual removal +doctor --fixmigration is tracked in #91243.)Did security, auth, secrets, network, or tool execution behavior change? (
Yes/No)No.
What is the highest-risk area?
The capability latch and the back-compat merge decision. Mis-scoping the latch (per-bucket instead of session) would reintroduce over-merging on new builds.
How is that risk mitigated?
The latch is session-scoped at the message ingress; the gate is unit-tested across all three branches; the back-compat path provably never merges more than shipped
main, so there is no regression for current users.Current review state
What is the next action?
Maintainer review. Safe to land ahead of the
imsgrelease because the back-compat path preserves shipped behavior; precision activates automatically once animsgbuild with openclaw/imsg#137 reaches users.What is still waiting on author, maintainer, CI, or external proof?
Nothing outstanding: CI is green, and live end-to-end proof against a real imsg #137 build has been captured (see proof comment). Only the imsg #137 release remains, which gates real-user activation but not correctness — the back-compat path keeps released-
imsgbehavior identical to shippedmain.