Expose the chat a message belongs to and match own messages by chat - #18
Merged
Merged
Conversation
Messages the current user sends from another device are synced to chat.db with handle_id = 0, so the participantHandles predicate (which only looked at message.handle_id) never returned them, and callers had no way to tell which conversation such a message belonged to. - Add Message.chatID, resolved through chat_message_join (the joined chat when the predicate already joins the chat tables, a per-message lookup otherwise). - participantHandles now also matches messages from the current user that belong to a chat with any of the given handles. - Add ChatPredicate.id to look a chat up by identifier. - Tests for all three, including a from-me message with handle_id = 0.
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is consistent with the described behavior and includes focused coverage for both query paths and edge cases.
Pull request overview
Adds chat context to messages and improves participant-based matching for synced outgoing messages.
Changes:
- Exposes
Message.chatIDand supports chat lookup by ID. - Includes the user’s outgoing messages when filtering by chat participants.
- Adds documentation and comprehensive database tests.
File summaries
| File | Description |
|---|---|
Sources/iMessage/Message.swift |
Adds optional chat identification. |
Sources/iMessage/FetchRequest.swift |
Extends message and chat predicates. |
Sources/iMessage/Database.swift |
Resolves chat IDs and updates SQL filtering. |
Tests/iMessageTests/DatabaseTests.swift |
Covers new query behavior and edge cases. |
README.md |
Documents participant semantics and chat lookup. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Owner
|
Thanks for this, @patp. Matching own messages through the chat is the right fix for the synced-from-iPhone case. Merging now. |
patp
added a commit
to patp/iMCP
that referenced
this pull request
Sep 4, 2026
Messages the user sends from another device are synced to chat.db without a sender handle, so until now most of the user's own messages came back with sender "me" and nothing to tie them to a conversation; a client could not thread them or tell who they were sent to. Each message now carries an "isPartOf" Conversation with the chat identifier, its display name (group chats) and its participants. Chats are looked up once per call and shared by their messages. Relies on Message.chatID and ChatPredicate.id from Madrid 0.5.0 (mattt/Madrid#18), which the project already depends on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYn7auXKTFrqS1PSR6rQKn
mattt
added a commit
to mattt/iMCP
that referenced
this pull request
Sep 25, 2026
Messages the user sends from another device are synced to chat.db without a sender handle, so until now most of the user's own messages came back with sender "me" and nothing to tie them to a conversation; a client could not thread them or tell who they were sent to. Each message now carries an "isPartOf" Conversation with the chat identifier, its display name (group chats) and its participants. Chats are looked up once per call and shared by their messages. Relies on Message.chatID and ChatPredicate.id from Madrid 0.5.0 (mattt/Madrid#18), which the project already depends on. Co-authored-by: patp <boumagent@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Messages the current user sends from another device (iPhone, iPad — synced through iCloud) land in
chat.dbwithmessage.handle_id = 0. TheparticipantHandlespredicate only looks atmessage.handle_id, so those messages are never returned when fetching a conversation with someone, andMessagegives callers no way to tell which chat such a message belongs to.On my own database that is most of what I send: of 2 610 outgoing messages over six months, only 715 carry a handle; 1 660 are 1:1 messages with
handle_id = 0. The only thing tying them to a conversation ischat_message_join.Changes
Message.chatID: Chat.ID?— resolved throughchat_message_join. When the predicate already joins the chat tables (.chatID), the joined chat is reported; otherwise a correlated subquery looks the chat up per message (it hitschat_message_join_idx_message_id_only, checked withEXPLAIN QUERY PLANon a real database).MessagePredicate.participantHandlesalso matches messages from the current user that belong to a chat with any of the given handles (is_from_me = 1+chat_message_join/chat_handle_join). Inbound messages keep the previous semantics — only the given senders — so what other people wrote in a shared group chat is still excluded.ChatPredicate.id(Chat.ID)— look a chat up by identifier, e.g. from a message'schatID. The typed request API had no way to do that.chatIDon both query paths and for a message linked to no chat; a from-me message withhandle_id = 0in a chat; the three participant cases (handle in one chat, in both chats, other member's messages excluded); chat lookup by id. Existing expectations that only counted inbound messages were updated (testFetchMessagesByParticipant,testFetchMessages,testMessagePredicateComposition).Message's memberwise initializer gains achatID:parameter (it is internal, so no public API change); theCodablerepresentation gains an optionalchatIDkey.Why
I hit this through iMCP's
messages_fetch: a client can't thread the user's own replies, because most of them come back with neither a handle nor a conversation. A follow-up PR there useschatIDandChatPredicate.idto report the conversation on each message.