toeverything/AFFiNE · error · CopilotSessionNotFound
copilot_session_not_found
copilot_session_not_found
Error message
Copilot session not found.
What it means
prepareTurn (the entry point for preparing a conversation turn/stream) loads the session and requires session.config.userId === userId; when the session is missing or owned by a different user it throws CopilotSessionNotFound (code `copilot_session_not_found`, status `resource_not_found`). Ownership is enforced before any message handling.
Source
Thrown at packages/backend/server/src/plugins/copilot/runtime/hosts/conversation-host.ts:343
sessionId,
turnId: turn.id ?? '',
});
session.pushPersistedTurn(turn);
return {
turn,
quotaBackedRoutesAllowed: quotaAllowed,
};
}
async prepareTurn(
userId: string,
sessionId: string,
query: Record<string, string | string[]>
): Promise<PreparedConversationTurn> {
const { messageId, retry, params } = ChatQuerySchema.parse(query);
const session = await this.sessions.get(sessionId);
if (!session || session.config.userId !== userId) {
throw new CopilotSessionNotFound();
}
const appended = await this.appendSessionMessage(
userId,
session,
sessionId,
messageId,
retry
);
const currentUserMessage =
session.stashTurns.findLast(turn => turn.role === 'user') ??
appended.turn;
return {
messageId,
params,
session,
latestTurn: currentUserMessage,
quotaBackedRoutesAllowed: appended.quotaBackedRoutesAllowed,View on GitHub (pinned to b4c8548c09)
Solutions
- List the user's copilot sessions first and only stream ids that appear there
- On this 404, create a new session and re-send the message instead of retrying the dead id
- Verify the authenticated user matches the account that owns the conversation (check token/user id)
Defensive patterns
Strategy: fallback
Validate before calling
// guard: only stream sessions the user owns and that still exist
const sessions = await fetchCopilotSessions(userId, workspaceId);
const mine = sessions.find(s => s.id === sessionId);
if (!mine) {
sessionId = (await createCopilotSession({ variables: { options } })).data!; // fresh session
}
await prepareTurn(userId, sessionId, query); Type guard
function isSessionNotFound(e: unknown): boolean {
return (e as { extensions?: { code?: string } })?.extensions?.code === 'copilot_session_not_found';
} Try / catch
try {
await prepareTurn(userId, sessionId, query);
} catch (e) {
if (isSessionNotFound(e)) {
const { data } = await createCopilotSession({ variables: { options } }); // fallback: recreate
return prepareTurn(userId, data, query);
}
throw e;
} Prevention
- Open streams only for ids present in the freshly fetched session list
- Handle shared conversation links by validating the session against the current user first
- Ensure the auth token belongs to the account that owns the session
When it happens
Trigger: Opening a chat stream with a stale sessionId (session deleted/cleaned), a session id from another user (shared/leaked link), or an auth mismatch where the token's user id differs from the session owner.
Common situations: Deep links to old conversations after cleanup; multi-account browsers sending one account's token with another account's session link; session deleted in another tab; tests with mismatched user fixtures.
Related errors
- copilot_session_not_found
- copilot_session_not_found
- blob_not_found
- Workspace not found
- Session not found
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/2ff71815975799c0.
Report an issue: GitHub.