toeverything/AFFiNE · error · CopilotMessageNotFound

copilot_message_not_found

copilot_message_not_found

Error message

Copilot message ${messageId} not found.

What it means

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.

Source

Thrown at packages/backend/server/src/plugins/copilot/runtime/hosts/conversation-host.ts:166

      ) {
        throw new CopilotSelectedSourcesLimitExceeded();
      }
      throw error;
    }
    const scopeSnapshot = TurnScopeSnapshotSchema.parse(compiledScope);
    return { artifacts, focus, metadata, scopeSnapshot };
  }

  private async loadAcceptedTurn(
    session: ChatSession,
    sessionId: string,
    messageId: string,
    retry: boolean
  ): Promise<Turn | undefined> {
    const accepted = await this.submissions.getAccepted(messageId);
    if (!accepted) return;
    if (accepted.sessionId !== sessionId) {
      throw new CopilotMessageNotFound({ messageId });
    }

    if (retry) {
      await this.sessions.revertLatestMessage(sessionId, false);
      session.revertLatestMessage(false);
    }

    const existingTurn = session.findTurn(accepted.turnId);
    if (existingTurn) return existingTurn;

    const acceptedMessage = await this.sessions.getMessage(
      sessionId,
      accepted.turnId
    );
    if (acceptedMessage.role !== 'user') {
      throw new CopilotMessageNotFound({ messageId: accepted.turnId });
    }

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Always take messageId and sessionId from the same session payload (e.g. the history query result)
  2. After forking, refresh history and use the new session's message ids
  3. On this 404, refetch session history and retry with the correct id pair
Defensive patterns

Strategy: validation

Validate before calling

// guard: only use message ids that belong to this session
const history = await fetchHistory(sessionId);
const valid = history.messages.some(m => m.id === messageId);
if (!valid) throw new Error('messageId does not belong to this session');
await prepareTurn(sessionId, messageId);

Type guard

function isMessageNotFound(e: unknown): boolean {
  return (e as { extensions?: { code?: string } })?.extensions?.code === 'copilot_message_not_found';
}

Try / catch

try {
  await prepareTurn(sessionId, messageId);
} catch (e) {
  if (isMessageNotFound(e)) {
    const fresh = await fetchHistory(sessionId); // re-pair ids from one payload
    const lastUser = fresh.messages.findLast(m => m.role === 'user');
    if (lastUser) return prepareTurn(sessionId, lastUser.id);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/66a94167ac300212. Report an issue: GitHub.