{"record":{"id":"3b12fab2e05bcd92","repo":"paperclipai/paperclip","slug":"photon-message-belongs-to-another-chat","errorCode":null,"errorMessage":"Photon message belongs to another chat","messagePattern":"Photon message belongs to another chat","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/photon/adapter.ts","lineNumber":294,"sourceCode":"        fullName: address,\n        isBot: false,\n        isMe: raw.isFromMe,\n        isSystem:\n          raw.isSystemMessage ||\n          raw.isServiceMessage ||\n          !address ||\n          (service !== \"iMessage\" && !raw.isFromMe),\n      },\n      metadata: {\n        dateSent: new Date(raw.dateCreated),\n        edited: !!raw.dateEdited,\n        editedAt: raw.dateEdited ? new Date(raw.dateEdited) : undefined,\n      },\n    });\n  }\n  normalize(raw: PhotonMessage, chat: PhotonChat): Message<PhotonMessage> {\n    if (!raw.chatGuids.includes(chat.guid))\n      throw new Error(\"Photon message belongs to another chat\");\n    return this.parseMessage({\n      ...raw,\n      chatGuids: [chat.guid],\n      paperclipIsGroup: chat.isGroup,\n    } as PhotonMessage);\n  }\n  async fetchMessage(id: string, messageId: string) {\n    const chat = await this.chatInfo(id);\n    return this.normalize(\n      await this.client.messages.get(messageId).catch((error) => {\n        throw photonFailure(error);\n      }),\n      chat,\n    );\n  }\n  async fetchMessages(id: string, options?: FetchOptions) {\n    const chat = await this.chatInfo(id);\n    const page = await this.client.messages","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/photon/adapter.ts#L276-L312","documentation":"normalize converts a raw Photon message into a Paperclip Message, scoped to a given PhotonChat. Every raw Photon message carries a chatGuids array; if the target chat's guid is not among them, the message belongs to a different chat and normalize throws 'Photon message belongs to another chat'. This is an internal consistency check used by fetchMessage and fetchMessages.","triggerScenarios":"fetchMessage/fetchMessages invoked with a chat whose guid is absent from raw.chatGuids — e.g. a cached chat object paired with messages fetched for a different chat, or a stale chat guid after a chat was migrated.","commonSituations":"Concurrent chat updates causing a chat record to be mismatched with its message batch; caching PhotonMessages by message id and re-normalizing them against a different PhotonChat; a chat guid changing server-side while the local cache still holds the old one.","solutions":["Ensure the PhotonChat passed to normalize is the same chat the messages were fetched for; fetch messages per chat rather than from a shared cache.","Refresh the chat list so chat guids are current before fetching messages.","Check raw.chatGuids.includes(chat.guid) before calling normalize and route the message to the matching chat instead."],"exampleFix":"// before\nconst normalized = adapter.normalize(rawMessage, cachedChat);\n// after\nconst chat = rawMessage.chatGuids.includes(cachedChat.guid)\n  ? cachedChat\n  : await getChatByGuid(rawMessage.chatGuids[0]);\nconst normalized = adapter.normalize(rawMessage, chat);","handlingStrategy":"type-guard","validationCode":"if (!raw.chatGuids.includes(chat.guid)) { /* mismatch: resolve correct chat first */ }","typeGuard":"const belongsToChat = (raw: PhotonMessage, chat: PhotonChat) => raw.chatGuids.includes(chat.guid);","tryCatchPattern":"try {\n  return adapter.normalize(raw, chat);\n} catch (e) {\n  if (e.message === \"Photon message belongs to another chat\") return resolveAndNormalize(raw);\n  throw e;\n}","preventionTips":["Fetch messages per chat and normalize immediately; avoid cross-chat message caches.","Refresh chat guids after any Photon chat migration.","Key message caches by (chatGuid, messageId), not messageId alone."],"tags":["photon","messaging","data-consistency"],"backgroundTag":"invalid-argument-value","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}