iMessage channel: self-chat detection fails silently when self-chat runs over the phone number (SELF built from message.account only)

Status Open
Reported on v2.1.226
Maintainer reply None cached
Activity 0 comments · opened Aug 11, 2026

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-official 0.1.0
  • Started with claude --channels plugin:imessage@claude-plugins-official
  • Full Disk Access granted, server running (verified via ps; bun server.ts alive)

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)

  1. SELF is built at boot from SELECT DISTINCT account FROM message WHERE is_from_me = 1 …. On this machine every row is E:<appleid-email> — the phone number never appears in account, even for messages sent from the iPhone via the number.
  2. The self-chat conversation's handle_id is the phone number. isSelfChat = SELF.has(sender) → false.
  3. 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 when isSelfChat. 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 of SELF handles.

Suggestions

  • Derive SELF from more than message.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.

View original on GitHub ↗