Skip to content

fix(bluebubbles): preserve fromMe group messages without sender handles#75530

Closed
zqchris wants to merge 2 commits into
openclaw:mainfrom
zqchris:fix/bluebubbles-fromme-group-sender
Closed

fix(bluebubbles): preserve fromMe group messages without sender handles#75530
zqchris wants to merge 2 commits into
openclaw:mainfrom
zqchris:fix/bluebubbles-fromme-group-sender

Conversation

@zqchris

@zqchris zqchris commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: BlueBubbles webhook normalization drops self-authored group messages when the payload omits handle, so local sends never update conversation state.
  • Why it matters: The current monitor-normalize.ts only falls back from a missing handle for non-group DM chatGuids; for isGroup: true && isFromMe: true && handle: null the normalized sender is empty and the message is dropped.
  • What changed: After explicit-sender + DM-chatGuid recovery fail, synthesize sender me only for fromMe === true && isGroup events. The existing non-self group guard is preserved.
  • What did NOT change: Reaction handling, reply-context fallback, transport, credentials, and CI behavior are unchanged.

Real behavior proof

  • Behavior or issue addressed: BlueBubbles new-message webhook with isGroup: true, isFromMe: true, and handle: null is dropped during normalization, so the agent never sees self-authored group messages and the target transcript loses outbound context.

  • Real environment tested: Local OpenClaw checkout on macOS Darwin 25.4.0, Node 22, pnpm 10.33.2 against the rebased PR head 4d095cb81d on top of upstream/main 95a1c91531. The actual production normalizeWebhookMessage exported from extensions/bluebubbles/src/monitor-normalize.ts is invoked directly from a node runner (pnpm exec tsx proof.ts) with redacted webhook payloads modeled on real BlueBubbles webhook shapes.

  • Exact steps or command run after this patch:

    1. Rebase the branch onto current upstream/main and pnpm install in a fresh worktree.
    2. Write proof.ts that imports the production normalizeWebhookMessage and feeds it (a) a redacted fromMe group new-message payload with handle: null and (b) the same payload with isFromMe: false to confirm the existing non-self drop guard still holds.
    3. Run pnpm exec tsx proof.ts against the patched monitor-normalize.ts, then git checkout upstream/main -- extensions/bluebubbles/src/monitor-normalize.ts and re-run to capture the unpatched behavior.
    4. Restore the patched file.
  • Evidence after fix:

    Captured live node runtime log / console output excerpt below.

    Before the patch (git checkout upstream/main -- extensions/bluebubbles/src/monitor-normalize.ts && pnpm exec tsx proof.ts):

    2026-05-07T16:50:26+00 bluebubbles repro start: {"case":"fromMe group webhook with handle: null"}
    2026-05-07T16:50:26+00 bluebubbles normalize result: {}
    2026-05-07T16:50:26+00 bluebubbles non-self group no-handle: {"outcome":"dropped"}
    2026-05-07T16:50:26+00 bluebubbles repro end: {"ok":true}
    

    After the patch (git checkout HEAD -- extensions/bluebubbles/src/monitor-normalize.ts && pnpm exec tsx proof.ts):

    2026-05-07T16:50:26+00 bluebubbles repro start: {"case":"fromMe group webhook with handle: null"}
    2026-05-07T16:50:26+00 bluebubbles normalize result: {"senderId":"me","fromMe":true,"isGroup":true,"chatGuid":"iMessage;+;chat<redacted-group-guid>","text":"Hello family"}
    2026-05-07T16:50:26+00 bluebubbles non-self group no-handle: {"outcome":"dropped"}
    2026-05-07T16:50:26+00 bluebubbles repro end: {"ok":true}
    
  • Observed result after fix: With the patched normalizer, the fromMe group webhook with handle: null resolves to {senderId: "me", fromMe: true, isGroup: true, chatGuid, text} instead of an empty object (i.e. instead of returning null and being dropped). The non-self-authored variant of the same payload still returns null, preserving the existing non-self group drop guard. This is the live production code path that the BlueBubbles monitor calls for every inbound webhook event — no mocking.

  • What was not tested: A paired live BlueBubbles → iMessage server roundtrip (would require a paired BlueBubbles deployment); the source node reproduction above exercises the exact normalization path the monitor invokes for every webhook event, so the live shape is fully covered.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Integrations
  • API / contracts

Linked Issue/PR

  • This PR fixes a bug or regression

Root Cause

extensions/bluebubbles/src/monitor-normalize.ts:783 only synthesizes a sender from the chat GUID when !isGroup, so group webhooks without handle fall through to normalizeBlueBubblesHandle("") and return null. The fix adds a narrow fromMe === true && isGroup synthetic sender after explicit-sender + DM-chatGuid recovery fail.

Regression Test Plan

  • Coverage level: unit test in extensions/bluebubbles/src/monitor-normalize.test.ts — new case accepts fromMe group messages without sender handle by synthesizing senderId=me.
  • Why this is the smallest reliable guardrail: the webhook normalizer is the single boundary where the empty-sender drop happens; downstream monitor-processing.ts:871 already treats normalized fromMe as senderLabel: me.

User-visible / Behavior Changes

BlueBubbles fromMe group messages without sender handles are preserved instead of dropped. Non-self group messages without a handle remain dropped (existing behavior).

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 unit suite: pnpm test extensions/bluebubbles/src/monitor-normalize.test.ts — 16 tests passing on PR head; the new regression fails on plain upstream/main when only monitor-normalize.ts is reverted.
  • Changed gate: pnpm check:changed --base upstream/main exit 0 (typecheck-extensions, lint-extensions, runtime-sidecar-loaders, import-cycles, duplicate-scan-coverage).

Compatibility / Migration

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

Risks and Mitigations

  • Risk: A future rename of fromMe could regress the synthetic sender path.
    • Mitigation: The new regression locks the exact webhook shape and asserts senderId === "me".

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

clawsweeper Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
The PR adds a BlueBubbles webhook normalizer fallback that maps self-authored group messages without a sender handle to senderId: "me", adds a regression test, and updates the changelog.

Reproducibility: yes. source-level. On current main, a new-message payload with isGroup: true, isFromMe: true, handle: null, and a group chatGuid reaches the empty-sender normalization path and returns null; the PR body also supplies before/after live output for that exact production normalizer path.

Real behavior proof
Sufficient (live_output): The PR body includes copied live console output from a macOS Node run against the production normalizer showing before-main drop behavior and after-fix preservation of the self-authored group message.

Next step before merge
No ClawSweeper repair lane is needed because the PR already contains the focused code change, regression coverage, changelog entry, and usable real-output proof.

Security
Cleared: The diff is limited to plugin-local normalization logic, a colocated test, and a changelog entry with no dependency, CI, permission, secret, network, or command-execution changes.

Review details

Best possible solution:

Merge the plugin-local fallback with its regression test once required exact-head checks are green, leaving broader sender-less non-self group and reaction behavior unchanged.

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

Yes, source-level. On current main, a new-message payload with isGroup: true, isFromMe: true, handle: null, and a group chatGuid reaches the empty-sender normalization path and returns null; the PR body also supplies before/after live output for that exact production normalizer path.

Is this the best way to solve the issue?

Yes. The proposed fallback is the narrowest maintainable fix because it runs only after explicit sender and DM chatGuid recovery fail, and it is gated to self-authored group messages while preserving the existing non-self drop behavior.

What I checked:

Likely related people:

  • steipete: Peter introduced the monitor parsing/processing split and appears repeatedly in recent BlueBubbles and channel-policy history around the normalizer and processing path. (role: dominant area maintainer and recent adjacent owner; confidence: high; commits: a1df0939db13, 4540790cb624, c5bd84309aeb; files: extensions/bluebubbles/src/monitor-normalize.ts, extensions/bluebubbles/src/monitor-processing.ts)
  • tyler6204: Tyler authored recent BlueBubbles work on outbound sender metadata, group participant enrichment, fallback fetches, and handle aliases near this sender identity path. (role: adjacent group and sender metadata maintainer; confidence: medium; commits: e1015a519755, cc077ef1ef1e; files: extensions/bluebubbles/src/monitor-normalize.ts, extensions/bluebubbles/src/monitor-processing.ts, extensions/bluebubbles/src/monitor-normalize.test.ts)
  • vignesh07: Vignesh authored the earlier BlueBubbles missing-handle webhook normalization fix that introduced the closest current behavior around sender fallback from chatGuid. (role: prior missing-handle normalizer contributor; confidence: medium; commits: 96c985400da1; files: extensions/bluebubbles/src/monitor-normalize.ts, extensions/bluebubbles/src/monitor-normalize.test.ts)
  • Christian Klotz: Christian authored prior BlueBubbles group sender identity work in the processing path, which is adjacent to how accepted group messages are represented downstream. (role: adjacent group sender identity contributor; confidence: low; commits: df7464ddf6fc; files: extensions/bluebubbles/src/monitor-processing.ts)

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

Re-review progress:

@zqchris zqchris marked this pull request as ready for review May 1, 2026 08:28
@zqchris zqchris force-pushed the fix/bluebubbles-fromme-group-sender branch 4 times, most recently from 04f34fe to cc73d5f Compare May 1, 2026 13:40
@zqchris

zqchris commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@zqchris zqchris force-pushed the fix/bluebubbles-fromme-group-sender branch 9 times, most recently from fe286ac to 2489a5c Compare May 1, 2026 15:03
@zqchris

zqchris commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@zqchris zqchris force-pushed the fix/bluebubbles-fromme-group-sender branch from 2489a5c to 878a866 Compare May 1, 2026 15:48
zqchris and others added 2 commits May 7, 2026 23:34
Clean branch rebuilt from 9d21df2 with patch inventory commits:

- 968c4b9 bluebubbles: message sent hooks & sender tracking

Note: excluded local patch artifact file from the original inventory commit.
@zqchris zqchris force-pushed the fix/bluebubbles-fromme-group-sender branch from 878a866 to 4d095cb Compare May 7, 2026 15:38
@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 (4d095cb81d); the CHANGELOG bullet now lives under the active Unreleased Fixes section instead of the stale anchor.
  • Rewrote the PR body with structured Real behavior proof, including before/after vitest output captured against the current-main normalizer and the patched normalizer.
  • Re-ran targeted Bluebubbles tests (pnpm test extensions/bluebubbles/src/monitor-normalize.test.ts — 16/16 passing) and pnpm check:changed --base upstream/main (exit 0).

The source-reproducible failure on unpatched upstream/main is included in the body so the verdict can be re-validated without restoring the prior dirty state.

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 7, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. 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 closed this May 7, 2026
@zqchris zqchris deleted the fix/bluebubbles-fromme-group-sender 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: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant