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
- Refetch the session list (copilotSessions query) and confirm the id still exists before updating
- On this 404, drop the stale session from local state and stop updating it
- 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
- Drive updates from a freshly fetched session list, not long-lived cached ids
- Remove sessions from local state when cleanup/delete succeeds anywhere in the app
- Never reuse docId/messageId where sessionId is expected
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
- Workspace not found
- Session not found
- too_many_request
- copilot_session_not_found
- copilot_transcription_job_not_found
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/e4ebf723ea8f2386.
Report an issue: GitHub.