[Bug] iMessage plugin: self-chat detection fails on email-only SELF set + iCloud-synced multi-device chat.db row pattern
Bug: iMessage plugin self-chat detection fails when SELF set is email-only + phone-number chat row has is_from_me=1 only (multi-device iCloud sync environment)
Plugin / Versions
- Plugin:
imessage@claude-plugins-officialv0.1.0 (installed viaclaude plugin install, scope=user) - bun runtime: 1.3.14 (
brew install oven-sh/bun/bun) - Claude Code CLI: 2.1.150
- macOS: 15.7.5 (24G624)
- iPhone iOS: 26.5 (same Apple ID, iCloud Messages sync ON)
Symptom
After successful claude --channels plugin:imessage@claude-plugins-official launch (channel server starts, Listening for channel messages from: plugin:imessage@claude-plugins-official displays in TUI, bun process spawns and opens chat.db), inbound iMessages from self do not reach the Claude session, even though:
- Full Disk Access is granted
- The bun server (
/usr/local/bin/bun server.ts) is alive and confirmed reading chat.db (vialsof) - chat.db sees the new messages (verified by direct SQLite query)
- The channel sees them in poll loop (verifiable indirectly — see below)
Root cause (analysis from server.ts source)
The plugin's self-chat detection in server.ts at line 182 builds the SELF set from message.account rows:
SELECT DISTINCT account AS addr FROM message WHERE is_from_me = 1 AND account IS NOT NULL AND account != '' LIMIT 50
In my environment, this returns only email-based account identifiers:
e:user@example.com
e:
E:
E:user@example.com
Phone numbers (e.g. +15551234567) are not in the SELF set, even though my Apple ID is associated with both the email and a phone number, and the phone number is a valid iMessage handle for my account.
Then at line 804: const isSelfChat = !isGroup && SELF.has(sender.toLowerCase()) — for inbound messages where handle_id = +15551234567 (my own number), isSelfChat = false and the message goes through gate() (line 808), which drops with empty allowFrom under default allowlist policy.
Second symptom (chat.db row count variation)
Adding to the complexity: in my environment, email-addressed self-chat (chat_identifier = user@example.com) only records 1 row (is_from_me=1 with content in attributedBody), not the 2 rows (is_from_me=1 sent-receipt + is_from_me=0 content copy) that server.ts comment line 795-797 expects:
// Never deliver our own sends. In self-chat the is_from_me=1 rows are empty
// sent-receipts anyway — the content lands on the is_from_me=0 copy below.
if (r.is_from_me) return
Verified with direct SQLite query:
SELECT m.is_from_me, COUNT(*) FROM message m
JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
JOIN chat c ON c.ROWID = cmj.chat_id
WHERE c.chat_identifier = 'user@example.com'
GROUP BY m.is_from_me;
-- result: only is_from_me=1, no is_from_me=0 copy
In contrast, phone-number-addressed self-chat (chat_identifier = +15551234567) records 2 rows correctly:
is_from_me=1, length(attributedBody)=184 (sent-receipt)
is_from_me=0, length(attributedBody)=184 (content copy)
So:
- Email-addressed self-chat: 1 row only → caught by
if (r.is_from_me) return→ dropped - Phone-number-addressed self-chat: 2 rows correctly, content available in is_from_me=0 copy, but the handle is not in SELF set (email-only query) → treated as non-self → dropped by allowlist gate
Net effect: both self-chat paths are broken in this environment.
Workaround (verified working)
Add the user's own phone number to the allowlist:
/imessage:access allow +15551234567
This bypasses the broken self-chat detection by routing through the allowlist path. Permission Relay then works correctly (verified: bietf/krmoh/gwfef test sequence with iPhone yes-replies completing the relay).
But this is a workaround, not a fix — the user has to know their own number is missing from SELF, and the allowlist semantically means "this contact is allowed", not "this is me".
Suggested fixes
- Expand SELF set query: Also extract phone-number addresses from the user's Apple ID, e.g. via
handle.idwherehandle.uncanonicalized_idmatches the user's own numbers, or viachat.last_addressed_handle(already mentioned in README as a source). - Handle multi-device iCloud sync gracefully: When chat.db only has the
is_from_me=1row (nois_from_me=0copy), the plugin should still extract content fromattributedBodyand route as self-chat if the addressee matches the user. Currently,if (r.is_from_me) returnshort-circuits this. - Document the prerequisite explicitly: The README mentions chat.db reading + AppleScript but doesn't note that the implementation assumes specific chat.db row patterns that vary between Mac-only and multi-device-iCloud-sync environments.
Related: bun prerequisite not documented
This is filed as a separate companion bug, but for context: the plugin requires bun runtime (per package.json script "start": "bun install --no-summary && bun server.ts") but the README doesn't list bun as a prerequisite — it just says "the prompt names whatever app launched bun." This caused several wasted hours assuming the plugin should work post-install without a separate bun install step.
Why this matters
Without these fixes, the plugin doesn't work out-of-the-box for users with the very common iPhone + Mac + iCloud Messages sync configuration — which is arguably the intended user base for this plugin (the whole point is to send instructions from iPhone to Mac Claude Code, so users with both devices on the same Apple ID are the target demographic).
Happy to provide additional logs / queries / repro environment.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗