Skip to content

iMessage module: fix sending on modern macOS, see images, survive message-to-self and iCloud re-sync - #1835

Open
J0UH wants to merge 2 commits into
danielmiessler:mainfrom
J0UH:imessage-hardening
Open

iMessage module: fix sending on modern macOS, see images, survive message-to-self and iCloud re-sync#1835
J0UH wants to merge 2 commits into
danielmiessler:mainfrom
J0UH:imessage-hardening

Conversation

@J0UH

@J0UH J0UH commented Aug 13, 2026

Copy link
Copy Markdown

I set LifeOS up on my Mac this week and made the iMessage module my main channel, with a twist: I text my own number, so the DA lives in my "messages to yourself" thread. That surfaced a string of bugs, a couple of which will bite any iMessage user, not just the self-chat case. Everything here is running on my machine now (macOS 26.5, LifeOS 7.28.3).

Sending was broken outright. participant targetService handle "..." is not a valid AppleScript object specifier, at least on current macOS. osascript exits 1, every reply silently fails, and the SDK sessions still run and cost money. The working form is participant "..." of targetService. That's the first commit, and it's independent of everything else if you'd rather cherry-pick it.

The second commit is the rest:

Images. The module only read chat.db's text column, so photos were invisible, and attachment-only messages never even reached the bot because of the text IS NOT NULL filter. Attachments now get staged into the module's state dir before the session starts. The copy step matters: only the bun process holds Full Disk Access, the claude subprocess it spawns does not, so it can't read ~/Library/Messages itself. HEIC converts to JPEG with sips, and the staged paths go into the prompt for the Read tool. Tested with photos and screenshots, the DA describes them fine.

Message-to-self. If the allowlisted handle is your own Apple ID, every reply the bot sends comes back as an is_from_me=0 row, and the poller treats it as a new user message. Mine answered itself 255 times before I pulled the plug. Replies now carry a zero-width-space prefix and the poller skips anything that starts with one. There's a second problem in the same setup: iCloud does not reliably generate that received-echo copy of your own sent messages. Mine stopped generating them mid-day and the bot just went deaf. The poller now also accepts is_from_me=1 rows, but only inside a chat whose identifier is on the allowlist, with a dedup set for when an echo copy shows up late. Side benefit: you can talk to the DA straight from Messages.app on the Mac.

iCloud re-sync. This one cost me a morning. Overnight, iCloud re-inserted my entire previous day of messages under brand-new ROWIDs with their original timestamps. The cursor is ROWID-based, so at 8:46am the bot saw ~240 "new" messages and answered every single one, including naming each emoji I'd sent the day before. In Danish. The fix is a freshness gate: anything whose own timestamp is older than 15 minutes gets skipped no matter what ROWID it arrives on. ROWID order just isn't arrival order after a re-sync or a restore, so this protects every user of the module.

Dropped messages. Anything sent while a session was in flight got "Still processing your previous message. Please wait." and was then lost forever, because the cursor had already moved past it. poll() now returns early while processing, so pending messages stay in the DB and get handled in order on the next tick.

Happy to split any of this out or adjust the freshness window if you want it configurable.

J0UH added 2 commits August 13, 2026 08:56
…work on modern macOS

participant targetService handle "..." is not a valid object specifier and
makes osascript exit 1, so every reply silently failed while the SDK
sessions still ran. The working form is participant "..." of targetService.
Verified on macOS 26.5.
…d re-sync guard

Four fixes from running the module as a daily channel (texting my own
number, macOS 26.5):

- Attachments: read the attachment table, stage copies into the module
  state dir (the claude subprocess has no Full Disk Access — only the
  bun process does), convert HEIC to JPEG via sips, and hand the staged
  paths to the session for the Read tool. Attachment-only messages were
  previously dropped by the text IS NOT NULL filter.

- Message-to-self: replies now carry a zero-width-space prefix and the
  poller ignores anything starting with one, otherwise the bot answers
  its own echo forever (mine got 255 turns in). iCloud also fails to
  reliably generate the received-echo copy of your own sent messages,
  so is_from_me=1 rows are now accepted, but only in a chat whose
  identifier is allowlisted, with a dedup set for late echo copies.

- Freshness gate: iCloud can re-insert old messages under new ROWIDs
  with their original timestamps. The ROWID cursor then treats the
  entire history as new — mine answered ~240 day-old messages one
  morning. Skip anything older than 15 minutes regardless of ROWID.

- Don't drop messages that arrive mid-session: poll() returns early
  while processing instead of advancing the cursor past them, so they
  process in order on the next tick. The old path replied 'Still
  processing' and then lost the message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant