toeverything/AFFiNE · error · CopilotSessionNotFound

copilot_session_not_found

copilot_session_not_found

Error message

Copilot session not found.

What it means

updateCopilotSession loads the target with chatSession.get(options.sessionId); when that returns null it throws CopilotSessionNotFound (code `copilot_session_not_found`, status `resource_not_found`). The session you tried to update does not exist (anymore) in the chat-session store.

Source

Thrown at packages/backend/server/src/plugins/copilot/resolver.ts:676

      messages: session.messages.map(message => ({
        ...message,
        id: message.id,
      })) as ChatMessageType[],
    };
  }

  @Mutation(() => String, {
    description: 'Update a chat session',
  })
  @CallMetric('ai', 'chat_session_update')
  async updateCopilotSession(
    @CurrentUser() user: CurrentUser,
    @Args({ name: 'options', type: () => UpdateChatSessionInput })
    options: UpdateChatSessionInput
  ): Promise<string> {
    const session = await this.chatSession.get(options.sessionId);
    if (!session) {
      throw new CopilotSessionNotFound();
    }

    const config = await this.assertPermission(user, session.config);
    const { workspaceId, docId: currentDocId } = config;
    const { docId: newDocId } = options;
    // check permission if the docId is changed
    if (newDocId !== undefined && newDocId !== currentDocId) {
      await this.assertPermission(user, { workspaceId, docId: newDocId });
    }

    const lockFlag = `${COPILOT_LOCKER}:session:${user.id}:${workspaceId}`;
    await using lock = await this.mutex.acquire(lockFlag);
    if (!lock) {
      throw new TooManyRequest('Server is busy');
    }

    return await this.chatSession.update({
      ...options,

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Refetch the session list (copilotSessions query) and confirm the id still exists before updating
  2. On this 404, drop the stale session from local state and stop updating it
  3. Verify the sessionId passed in UpdateChatSessionInput is the one returned by creation, not a docId or messageId
Defensive patterns

Strategy: fallback

Validate before calling

// guard: confirm the session still exists before updating
const { data } = await queryCopilotSessions({ workspaceId });
const exists = data.sessions.some(s => s.id === sessionId);
if (!exists) {
  removeStaleSession(sessionId); // drop from local state instead of updating
} else {
  await updateCopilotSession({ variables: { options } });
}

Type guard

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

Try / catch

try {
  await updateCopilotSession({ variables: { options } });
} catch (e) {
  if (isCopilotSessionNotFound(e)) {
    removeStaleSession(options.sessionId); // stop tracking the dead session
    return; // non-fatal: nothing to update
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the chatSessionUpdate mutation with a sessionId that was deleted, cleaned up by cleanupCopilotSessions, or fabricated/stale; renaming/pinning a session in a tab that has been open since before the session was removed elsewhere.

Common situations: UI keeps a stale session id after workspace cleanup or 'clear conversations'; user deleted the session in another tab or device; sessionId corrupted during state management (undefined spliced into variables).

Related errors


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