iMessage channel: self-chat detection fails silently when self-chat runs over the phone number (SELF built from message.account only)
Summary
The iMessage channel's self-chat detection derives "self" addresses solely from the message.account column of chat.db. For users whose Apple ID is an email address but whose self-chat conversation runs over their phone number, the documented "text yourself" onboarding flow fails silently: messages show up in Messages.app but never reach the session, with no error or log hint visible to the user.
Environment
- Claude Code 2.1.226 (native install, macOS, Darwin 25.6.0)
- Channels research preview,
imessage@claude-plugins-official0.1.0 - Started with
claude --channels plugin:imessage@claude-plugins-official - Full Disk Access granted, server running (verified via
ps;bun server.tsalive)
What happens
Following the docs (https://code.claude.com/docs/en/channels, iMessage tab): "Open Messages on any device signed into your Apple ID and send a message to yourself. It reaches Claude immediately: self-chat bypasses access control with no setup."
Sent a message to myself from my iPhone (self-chat over my phone number). The message arrives in Messages on the Mac, lands in chat.db as the expected pair (is_from_me=1 + is_from_me=0 receive copy) — but never reaches the session.
Root cause (from reading the plugin's server.ts)
SELFis built at boot fromSELECT DISTINCT account FROM message WHERE is_from_me = 1 …. On this machine every row isE:<appleid-email>— the phone number never appears inaccount, even for messages sent from the iPhone via the number.- The self-chat conversation's
handle_idis the phone number.isSelfChat = SELF.has(sender)→ false. - The message then goes through the normal gate: default
dmPolicy: "allowlist"with an empty list → dropped silently.
So whether self-chat detection works depends on which alias the user's self-chat happens to run over — number vs. Apple ID email — which the user has no reason to know about.
Second finding: the email-alias self-chat doesn't work either
Texting my own Apple ID email address instead (which would match SELF): those messages land in chat.db with only the is_from_me=1 row — no is_from_me=0 receive copy is ever created (measured directly in chat.db; sent from the iPhone, message visible in Messages on the Mac). Since handleInbound returns on r.is_from_me, the content never gets delivered. The code comment assumes "the content lands on the is_from_me=0 copy below", which doesn't hold for this alias configuration.
Net result: for this (presumably common) configuration, no self-chat variant reaches the session.
Workaround used, and its side effects
Adding the own phone number to allowFrom in access.json makes the channel work — but the chat is now treated as a regular allowlisted DM, not self-chat:
- Echo events: the echo filter (
consumeEcho) only applies whenisSelfChat. Every reply the assistant sends produces a receive copy in the self-chat, which passes the allowlist gate and re-enters the session as an inbound<channel>event (recognizable by the "Sent by Claude" signature). No hard loop observed, but every reply generates one spurious inbound event. - Permission relay unavailable: permission replies (
yes/no <code>) are only accepted from detected self-chat, and permission prompts are only sent to chats ofSELFhandles.
Suggestions
- Derive
SELFfrom more thanmessage.account— e.g. also treat handles of chats whose only participant is one of the user's own aliases as self, or read the "You can be reached at" aliases. - Apply the echo filter to all allowlisted chats, not just detected self-chat — it's keyed on recently-sent text per chat GUID, so this seems safe.
- On silent drops of would-be self-chat (sender handle equals a chat participant that is the only participant), a stderr hint would make this diagnosable without reading
server.ts.
Happy to provide more details from the measurements if useful.