Skip to content

iMessage: silent group message drop when channels.imessage.groups is unset #78749

Description

@omarshahine

Summary

The bundled iMessage plugin runs two separate group allowlist gates back-to-back in extensions/imessage/src/monitor/inbound-processing.ts:

  1. Sender / chat-target allowlistisAllowedIMessageSender against channels.imessage.groupAllowFrom. Matches by handle, chat_guid, chat_identifier, or chat_id.
  2. Group registryresolveChannelGroupPolicy at line 199. With groupPolicy: \"allowlist\":
    • allowlistEnabled = true
    • allowAll requires channels.imessage.groups[\"*\"] to exist
    • Without that wildcard (or an explicit per-chat_id entry), allowed = false → drop at line 336-340

The drop is logged via params.logVerbose?.(...) at line 337-339:

if (isGroup && groupListPolicy.allowlistEnabled && !groupListPolicy.allowed) {
  params.logVerbose?.(
    `imessage: skipping group message (${groupId ?? \"unknown\"}) not in allowlist`,
  );
  return { kind: \"drop\", reason: \"group id not in allowlist\" };
}

logVerbose only fires at logging.level: debug or finer. At the default info level, every group message is silently dropped with no log entry, no error response to the sender, and no telemetry.

Why it matters / who it bites

This is the most common BlueBubbles → bundled-iMessage migration failure mode (PR #78317 documents it). BlueBubbles operators copy groupAllowFrom and groupPolicy but skip the groups block, because BB's typical groups: { \"*\": { \"requireMention\": true } } looks like an unrelated mention setting — it's actually load-bearing for gate 2. After the cutover, group messages vanish silently. DMs continue to work because they take a different code path. Operators have no signal that anything is wrong unless they think to raise log level.

Reported by Omar Shahine on 2026-05-06 after live debugging revealed every group message Lobster received over iMessage since the migration had been silently dropped at gate 2.

Proposed fix (sketch — happy to iterate)

Either or both:

  1. Raise the drop's log severity from verbose to warn (or info). A drop that's invisible at the default log level is user-hostile. The drop is rate-limited per-message, so log volume is bounded by inbound message rate, which is already paced. A warn line per dropped group message is a strong "your config is incomplete" signal.

  2. Surface the misconfiguration at gateway start, not at message time. When groupPolicy === \"allowlist\" AND groups is missing/empty AND there are no per-chat_id entries, the gateway could log a one-time warn at startup like: \"channels.imessage.groupPolicy=allowlist with no channels.imessage.groups entries — every group message will be dropped. Add 'groups': { '*': { ... } } to allow all groups, or explicit chat_id entries.\". This catches the misconfiguration before any message lands.

  3. Optional: doctor check that catches the same condition during openclaw doctor.

Out of scope

  • Not changing the gate semantics — operators who genuinely want the strict allowlist behavior should keep it. This is purely about making the drop observable.

Tracking

Filed off the back of PR #78317. Documented as a footgun in docs/channels/imessage-from-bluebubbles.md ("Group registry footgun" section) and docs/channels/imessage.md (Group policy Warning block) in that same PR. This issue is for the underlying behavior change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    maintainerMaintainer-authored PR

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions