{"record":{"id":"66a94167ac300212","repo":"toeverything/AFFiNE","slug":"copilot-message-not-found","errorCode":"copilot_message_not_found","errorMessage":"Copilot message ${messageId} not found.","messagePattern":"Copilot message (.+?) not found\\.","errorType":"exception","errorClass":"CopilotMessageNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/plugins/copilot/runtime/hosts/conversation-host.ts","lineNumber":166,"sourceCode":"      ) {\n        throw new CopilotSelectedSourcesLimitExceeded();\n      }\n      throw error;\n    }\n    const scopeSnapshot = TurnScopeSnapshotSchema.parse(compiledScope);\n    return { artifacts, focus, metadata, scopeSnapshot };\n  }\n\n  private async loadAcceptedTurn(\n    session: ChatSession,\n    sessionId: string,\n    messageId: string,\n    retry: boolean\n  ): Promise<Turn | undefined> {\n    const accepted = await this.submissions.getAccepted(messageId);\n    if (!accepted) return;\n    if (accepted.sessionId !== sessionId) {\n      throw new CopilotMessageNotFound({ messageId });\n    }\n\n    if (retry) {\n      await this.sessions.revertLatestMessage(sessionId, false);\n      session.revertLatestMessage(false);\n    }\n\n    const existingTurn = session.findTurn(accepted.turnId);\n    if (existingTurn) return existingTurn;\n\n    const acceptedMessage = await this.sessions.getMessage(\n      sessionId,\n      accepted.turnId\n    );\n    if (acceptedMessage.role !== 'user') {\n      throw new CopilotMessageNotFound({ messageId: accepted.turnId });\n    }\n","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/runtime/hosts/conversation-host.ts#L148-L184","documentation":"loadAcceptedTurn found an accepted submission record for messageId, but its sessionId does not match the session the request is operating on, so it throws CopilotMessageNotFound (code `copilot_message_not_found`, status `resource_not_found`) with that messageId. The message exists — just in a different chat session.","triggerScenarios":"Retrying or continuing a conversation using a messageId that belongs to another session: ids mixed up after a fork (old session's message id sent to the forked session), or a client reusing cached ids across sessions.","commonSituations":"After forkCopilotSession, continuing with the pre-fork message ids; state-management bug pairing a messageId from session A with sessionId B; deep links carrying stale messageId query params.","solutions":["Always take messageId and sessionId from the same session payload (e.g. the history query result)","After forking, refresh history and use the new session's message ids","On this 404, refetch session history and retry with the correct id pair"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// guard: only use message ids that belong to this session\nconst history = await fetchHistory(sessionId);\nconst valid = history.messages.some(m => m.id === messageId);\nif (!valid) throw new Error('messageId does not belong to this session');\nawait prepareTurn(sessionId, messageId);","typeGuard":"function isMessageNotFound(e: unknown): boolean {\n  return (e as { extensions?: { code?: string } })?.extensions?.code === 'copilot_message_not_found';\n}","tryCatchPattern":"try {\n  await prepareTurn(sessionId, messageId);\n} catch (e) {\n  if (isMessageNotFound(e)) {\n    const fresh = await fetchHistory(sessionId); // re-pair ids from one payload\n    const lastUser = fresh.messages.findLast(m => m.role === 'user');\n    if (lastUser) return prepareTurn(sessionId, lastUser.id);\n  }\n  throw e;\n}","preventionTips":["Always source sessionId and messageId from the same history payload","After a fork, refetch the new session's history before continuing","Never cache message ids across sessions"],"tags":["copilot","chat-message","not-found","session-mismatch"],"backgroundTag":"message-not-found","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}