anthropics / anthropics/claude-plugins-official

imessage: self-chat silently dropped when self-chat handle (phone) differs from Apple ID account (email)

Open
#4,503 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
36.3k
Forks
4.1k
Avg merge
2d 14h
Merged PRs (30d)
539

Description

## Summary

The iMessage plugin silently drops **all** self-chat (Note-to-Self) messages when your self-chat is keyed by your **phone number** but your iMessage `account` identity is an **email** (the normal case for iPhone users). The README promises "self-chat bypasses access control," but in this configuration self-chat never bypasses anything — every message is dropped by the DM gate.

`external_plugins/imessage/server.ts`, v0.1.0, macOS.

## Root cause

`SELF` is built only from the `account` column of your sent messages:

```
// server.ts ~L181
SELECT DISTINCT account AS addr FROM message WHERE is_from_me = 1 AND account IS NOT NULL AND account != '' LIMIT 50
```

The `account` column holds your Apple ID **sending** identity, which for most people is an email (e.g. `E:you@example.com`). But a Note-to-Self chat is very commonly keyed by your **phone number** (`+1XXXXXXXXXX`), and that number never appears in `account`. So:

```
// server.ts L804
const isSelfChat = !isGroup && SELF.has(sender.toLowerCase()) // sender = "+1XXXXXXXXXX", SELF = {"you@example.com"} -> false
```

`isSelfChat` is `false`, so the message falls through to the DM gate, and with the default empty allowlist it is dropped:

```
// server.ts ~L333-335
if (!input.isGroup) {
if (access.allowFrom.includes(input.senderId)) return { action: 'deliver' }
if (access.dmPolicy === 'allowlist') return { action: 'drop' } // <- every self-chat message dies here
```

## Reproduction

1. On an iPhone-linked Mac where your self-chat thread is addressed by phone number (Messages shows a "You" thread keyed to your number) and your Apple ID is an email.
2. Launch with `claude --channels plugin:imessage@claude-plugins-official`.
3. Text yourself anything. It reaches `chat.db` but never injects into the session.

Verified against `chat.db`:
- The received (`is_from_me=0`) self-echo row has `handle_id -> +1XXXXXXXXXX` (phone), while `account = E:you@example.com` (email). These never match after `SELF.has(sender.toLowerCase())`.
- (Separately confirmed the body decodes fine via `parseAttributedBody` — `text` is NULL, content is in `attributedBody`. So decoding is not the problem; identity matching is.)

There is no structural way in `chat.db` to tie the phone-number handle back to "you" via `account`, so the current single-source `SELF` can't self-correct.

## Why a plain allowlist entry is not the fix

Adding your own number to `allowFrom` delivers the messages, but echo suppression (`consumeEcho`, L805) only runs under `isSelfChat`. Since it would still be `false`, the plugin's own outbound replies (which come back as `is_from_me=0` echoes on the self-chat) would loop back in as commands. The correct fix must make `isSelfChat` true.

## Proposed fix (two parts)

**1. Auto-detect self-chat handles (fixes the common case, no config).**
In addition to the `account` query, add to `SELF` the handle of any 1:1 chat that contains a *self-echo* — a message present as **both** `is_from_me=1` and `is_from_me=0` with matching text/`attributedBody` and near-identical timestamp. That duplication is characteristic of Note-to-Self chats and does not occur in normal DMs, so it reliably identifies your own phone/email handles without any configuration. (Can be scoped to recent messages for performance.)

**2. Explicit escape hatch (idiomatic, deterministic).**
Honor an `IMESSAGE_SELF_IDS` env var (comma-separated handles), appended to `SELF`, matching the existing `IMESSAGE_ACCESS_MODE` / `IMESSAGE_ALLOW_SMS` / `IMESSAGE_DB_PATH` convention. Covers multi-number setups, aliases, and CI. Sketch:

```ts
// after the existing SELF-building block
for (const id of (process.env.IMESSAGE_SELF_IDS ?? '')
.split(',')
.map((s) => s.trim().replace(/^[A-Za-z]:/, '').toLowerCase())
.filter(Boolean)) SELF.add(id)
```

Plus a note in README.md / ACCESS.md.

I've been running (2) locally as a patch and it fully resolves it. Happy to help validate a maintainer's implementation of (1).

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in external_plugins/imessage/server.ts with the SELF-building query around L181, the DM gate around L333-335, and isSelfChat/consumeEcho around L804-805. Reproduce the phone-versus-email identity mismatch against chat.db, then verify that self-chat handles are recognized, echo suppression still works, and IMESSAGE_SELF_IDS plus the README.md/ACCESS.md documentation cover the explicit configuration path.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, sqlite, typescript
Domain
authentication, backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.