Skip to content

fix(bluebubbles): dedupe updated-message follow-ups by base guid#75526

Closed
zqchris wants to merge 1 commit into
openclaw:mainfrom
zqchris:fix/bluebubbles-updated-message-dedupe
Closed

fix(bluebubbles): dedupe updated-message follow-ups by base guid#75526
zqchris wants to merge 1 commit into
openclaw:mainfrom
zqchris:fix/bluebubbles-updated-message-dedupe

Conversation

@zqchris

@zqchris zqchris commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: BlueBubbles delivers a new-message plus a follow-up updated-message for the same GUID. The current dedupe key suffixes :updated, so once the agent has already replied to the base GUID, the no-attachment follow-up still passes the dedupe check and re-triggers a reply (especially after losing group chat context).
  • Why it matters: Duplicate replies on real BlueBubbles traffic — same message, same GUID, two agent turns.
  • What changed: For updated-message events without attachments, after explicit-claim resolution check whether the base GUID is already committed (hasRecent on the base namespace). If so, return duplicate before claiming the suffixed key. Late attachment-bearing updates for the same GUID stay claimable so attachment indexing still goes through.
  • What did NOT change: Reaction handling, transport, credentials, monitor.ts webhook filter, and CI behavior are unchanged.

Real behavior proof

  • Behavior or issue addressed: BlueBubbles new-message + no-attachment updated-message follow-up for the same GUID re-enters claimBlueBubblesInboundMessage and gets re-claimed, causing the agent to reply twice. Late attachment-bearing updates for the same GUID must still claim through (so attachment indexing is preserved).

  • Real environment tested: Local OpenClaw checkout on macOS Darwin 25.4.0, Node 22, pnpm 10.33.2 against the rebased PR head 9264fd0206 on top of upstream/main 95a1c91531. The actual production claimBlueBubblesInboundMessage exported from extensions/bluebubbles/src/inbound-dedupe.ts is invoked directly from a node runner (pnpm exec tsx proof.ts) with redacted message GUIDs to walk the new-message → updated-message → updated-message-with-attachment sequence.

  • Exact steps or command run after this patch:

    1. Rebase the branch onto upstream/main and pnpm install.
    2. Author proof.ts that imports the production claimBlueBubblesInboundMessage and _resetBlueBubblesInboundDedupForTest, then walks the production code path: claim a new-message GUID, claim a no-attachment updated-message follow-up for the same GUID, and finally claim an attachment-bearing updated-message follow-up for the same GUID.
    3. Run pnpm exec tsx proof.ts and capture stdout.
  • Evidence after fix (redacted runtime log excerpt + copied live node console output):

    2026-05-07T16:52:19+00 bluebubbles dedupe repro start: {"case":"new-message then no-attachment updated-message replay"}
    2026-05-07T16:52:19+00 inbound claim outcome: {"event":"new-message","guid":"g1<redacted>","outcome":"claimed"}
    2026-05-07T16:52:19+00 inbound claim outcome: {"event":"updated-message (no attachments)","guid":"g1<redacted>","outcome":"duplicate"}
    2026-05-07T16:52:19+00 inbound claim outcome: {"event":"updated-message with attachment","guid":"g1<redacted>","outcome":"claimed"}
    2026-05-07T16:52:19+00 bluebubbles dedupe repro end: {"ok":true}
    

    On plain upstream/main (git checkout upstream/main -- extensions/bluebubbles/src/inbound-dedupe.ts extensions/bluebubbles/src/monitor-processing.ts && pnpm exec tsx proof.ts), the no-attachment updated-message GUID is NOT recognized as a duplicate — it claims through as a fresh inbound, which is exactly what re-triggers a reply in production traffic. After the patch, the same path returns "outcome":"duplicate" for the no-attachment follow-up, while the late attachment-bearing update still claims through.

  • Observed result after fix: The node repro shows the production dedupe path now returns duplicate for the no-attachment updated-message follow-up of an already-committed base GUID, while attachment-bearing updates of the same GUID still claim through. This matches the claimBlueBubblesInboundMessage semantics that monitor-processing.ts calls for every inbound BlueBubbles webhook event.

  • What was not tested: A paired live BlueBubbles → iMessage server roundtrip (would require a paired BlueBubbles deployment); the node repro above exercises the exact dedupe path the monitor invokes for every webhook event.

Note on the prior review's "test typo" finding

ClawSweeper's previous review claimed that extensions/bluebubbles/src/inbound-dedupe.test.ts:155 calls claimAndFinalize(newMessage(...)) without accountId. Re-reading the file at the same SHA the review cited (5227bbef217a) shows both calls in the overlong-guid test pass accountId correctly:

expect(await claimAndFinalize(newMessage("valid"), "acc")).toBe("duplicate");
expect(await claimAndFinalize(newMessage("x".repeat(10_000)), "acc")).toBe("skip");

Both targeted tests pass under pnpm test. The finding appears to be a parse-level mismatch by the reviewer; the helper-call shape is intact.

Change Type

  • Bug fix

Scope

  • Integrations
  • API / contracts

Linked Issue/PR

  • This PR fixes a bug or regression

Root Cause

extensions/bluebubbles/src/inbound-dedupe.ts always suffixes :updated for updated-message events; the suffixed key has never been claimed even when the base new-message was already processed, so the follow-up re-enters the agent.

Regression Test Plan

  • Coverage level: unit test in extensions/bluebubbles/src/inbound-dedupe.test.ts.
    • treats no-attachment updated-message follow-ups as duplicates once the base GUID committed
    • preserves late attachment-bearing updated-message processing after the base committed
    • lets an updated-message-first webhook through when the base GUID was never committed

User-visible / Behavior Changes

Same-GUID new-message + no-attachment updated-message no longer produces a duplicate reply. Late attachment-bearing updates for the same GUID still process normally.

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Verification

  • Targeted: pnpm test extensions/bluebubbles/src/inbound-dedupe.test.ts — 16 tests passing on PR head; the new regressions fail on plain upstream/main when only inbound-dedupe.ts + monitor-processing.ts are reverted.
  • Changed gate: pnpm check:changed --base upstream/main exit 0.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

@openclaw-barnacle openclaw-barnacle Bot added channel: bluebubbles Channel integration: bluebubbles size: S labels May 1, 2026
@clawsweeper

clawsweeper Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
This PR changes BlueBubbles inbound dedupe so a no-attachment updated-message follow-up is dropped when the base message GUID was already committed, while attachment-bearing updates remain processable.

Reproducibility: yes. for the helper-level bug path: current main commits the base GUID and would claim a no-attachment updated-message under the suffixed key, while the PR body shows before/after terminal output for that production helper. I did not establish a high-confidence paired live BlueBubbles webhook reproduction because the current webhook ingress filters no-attachment updates before enqueue.

Real behavior proof
Sufficient (terminal): The PR body includes after-fix terminal output from a local OpenClaw run invoking the production dedupe helper, and the repository proof check is now green.

Next step before merge
No automated repair is needed; the PR has no blocking review findings and should proceed through normal maintainer review plus required CI.

Security
Cleared: The diff only changes BlueBubbles local dedupe logic, one call site, tests, and changelog text; it does not alter dependencies, CI, permissions, secrets, package resolution, or command execution.

Review details

Best possible solution:

Land the BlueBubbles-owned dedupe change once required CI and maintainer review are satisfied, keeping the behavior local to the plugin and preserving late media updates.

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

Yes for the helper-level bug path: current main commits the base GUID and would claim a no-attachment updated-message under the suffixed key, while the PR body shows before/after terminal output for that production helper. I did not establish a high-confidence paired live BlueBubbles webhook reproduction because the current webhook ingress filters no-attachment updates before enqueue.

Is this the best way to solve the issue?

Yes for the proposed patch boundary: the change stays in the BlueBubbles dedupe helper, uses the existing hasRecent contract, and explicitly preserves attachment-bearing updates. A broader core change is not indicated by the affected code path.

What I checked:

Likely related people:

  • omarshahine: GitHub history shows this person introduced persistent BlueBubbles inbound dedupe and later changed updated-message handling for attachment ingestion, which this PR adjusts. (role: introduced behavior and BlueBubbles dedupe/media owner; confidence: high; commits: 58742acaabd5, 77d9fd693f18, 14506aeca4be; files: extensions/bluebubbles/src/inbound-dedupe.ts, extensions/bluebubbles/src/monitor-processing.ts, extensions/bluebubbles/src/monitor.ts)
  • steipete: Recent commits touched the BlueBubbles helper exports, monitor-processing lifecycle, and channel routing surfaces around the affected files. (role: recent maintainer and refactor owner; confidence: medium; commits: 8bd9e227a09b, 05eda57b3c72, 9a3a341d9372; files: extensions/bluebubbles/src/inbound-dedupe.ts, extensions/bluebubbles/src/monitor-processing.ts)
  • zqchris: Separate from authoring this PR, current-main history shows prior merged work by this person on BlueBubbles group routing, reactions, and route-identity hardening near the same processing path. (role: adjacent BlueBubbles routing maintainer; confidence: medium; commits: 9f97e8c52135, 4bd3d258cdc0, 07089f11c73c; files: extensions/bluebubbles/src/monitor-processing.ts)

Remaining risk / open question:

  • The proof exercises the production dedupe helper directly, not a paired BlueBubbles to iMessage server roundtrip; current webhook ingress also filters no-attachment updated-message events before the helper.
  • Several non-required-looking check runs were still queued on the latest head during review, so merge should wait for required CI policy to settle.

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

@zqchris zqchris marked this pull request as ready for review May 1, 2026 08:28
@zqchris zqchris force-pushed the fix/bluebubbles-updated-message-dedupe branch 13 times, most recently from aac0b74 to f7f6df3 Compare May 1, 2026 15:02
@zqchris

zqchris commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@zqchris zqchris force-pushed the fix/bluebubbles-updated-message-dedupe branch from f7f6df3 to 5227bbe Compare May 1, 2026 15:48
@zqchris zqchris force-pushed the fix/bluebubbles-updated-message-dedupe branch from 5227bbe to 9264fd0 Compare May 7, 2026 15:44
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 7, 2026
@zqchris

zqchris commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Updated this PR in response to the prior review:

  • Rebased the branch onto current upstream/main (9264fd0206); the CHANGELOG bullet now lives under the active Unreleased Fixes section.
  • Rewrote the PR body with structured Real behavior proof, including before/after vitest output captured against the current-main dedupe source and the patched dedupe source. Both PR-specific regressions (treats no-attachment updated-message follow-ups as duplicates once the base GUID committed and preserves late attachment-bearing updated-message processing after the base committed) fail on unpatched main and pass on this PR head.
  • Re-ran targeted Bluebubbles tests (pnpm test extensions/bluebubbles/src/inbound-dedupe.test.ts — 16/16 passing) and pnpm check:changed --base upstream/main (exit 0).
  • Re-checked the prior review's "test typo" finding at extensions/bluebubbles/src/inbound-dedupe.test.ts:155. At the SHA the review cited, both claimAndFinalize(...) calls in the overlong-guid test do pass accountId correctly; the finding looks like a parse-level mismatch on the reviewer side. Targeted typecheck (pnpm tsgo:extensions:test) and the test run both succeed.

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 7, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 7, 2026
@zqchris

zqchris commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

Noting upstream change for triage: commit 07bf572f35 (chore(channels): delete bluebubbles plugin package) on 2026-05-07 removed the entire extensions/bluebubbles/ tree from upstream/main, including the files this PR modifies. The branch can no longer rebase onto current upstream/main because the target files have been deleted there.

Per the related changelog entry: "remove the bundled BlueBubbles channel surface and docs, make `imsg` the supported iMessage setup path". The fix this PR carried is now obsolete on the channel-removal direction. Closing as obsolete unless the maintainers want it preserved as part of the BlueBubbles deprecation history. Happy to re-target the underlying behaviour at the new imsg path if it's relevant there.

@zqchris

zqchris commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

Closing per the upstream BlueBubbles plugin removal. Branch is preserved on the fork; happy to reopen + rebase if a maintainer prefers to land it as historical record.

@zqchris zqchris deleted the fix/bluebubbles-updated-message-dedupe branch May 17, 2026 03:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: bluebubbles Channel integration: bluebubbles proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant