toeverything/AFFiNE · error · CopilotSessionDeleted
copilot_session_deleted
copilot_session_deleted
Error message
Copilot session has been deleted.
What it means
CopilotSessionDeleted (action_forbidden / copilot_session_deleted) thrown by CopilotSessionModel.find (packages/backend/server/src/models/copilot-session.ts:472). find() looks up a root (parentSessionId null), no-action chat session matching userId/workspaceId/docId. If a matching row exists but its deletedAt is non-null (soft-deleted), the session cannot be reused and the guard throws.
Source
Thrown at packages/backend/server/src/models/copilot-session.ts:472
const extraCondition: Record<string, any> = {};
if (state.parentSessionId) {
// also check session id if provided session is forked session
extraCondition.id = state.sessionId;
extraCondition.parentSessionId = state.parentSessionId;
}
const session = await this.db.aiSession.findFirst({
where: {
userId: state.userId,
workspaceId: state.workspaceId,
docId: state.docId,
parentSessionId: null,
...this.noActionPromptCondition(),
...extraCondition,
},
select: { id: true, deletedAt: true },
});
if (session?.deletedAt) throw new CopilotSessionDeleted();
return session?.id;
}
@Transactional()
async getExists<Select extends Prisma.AiSessionSelect>(
sessionId: string,
select?: Select,
where?: Omit<Prisma.AiSessionWhereInput, 'id' | 'deletedAt'>
) {
return (await this.db.aiSession.findUnique({
where: { ...where, id: sessionId, deletedAt: null },
select,
})) as Prisma.AiSessionGetPayload<{ select: Select }> | null;
}
@Transactional()
async get(sessionId: string) {
return await this.getExists(sessionId, {View on GitHub (pinned to 26c515e050)
Solutions
- Stop reusing the deleted session: create a fresh one (reuseChat=false or clear the cached sessionId client-side).
- Before reusing, check session.deletedAt via getExists and skip reuse if set.
- If restoration is intended, clear deletedAt in the DB (operational step) before reusing.
- Client-side: on receiving copilot_session_deleted, evict the cached sessionId and retry as a new session.
Example fix
// before
const id = await sessionModel.create(state, /* reuseChat */ true);
// after
const existing = await sessionModel.getExists(state.sessionId, { id: true, deletedAt: true });
if (existing?.deletedAt) {
state.sessionId = undefined; // force new session
}
const id = await sessionModel.create(state, /* reuseChat */ true); Defensive patterns
Strategy: validation
Validate before calling
const existing = await sessionModel.getExists(state.sessionId, { id: true, deletedAt: true });
if (existing?.deletedAt) {
state.sessionId = undefined; // force new session
}
const id = await sessionModel.create(state, true); Type guard
const isReusable = (s: { id: string; deletedAt: Date | null } | null): s is { id: string; deletedAt: null } =>
s !== null && s.deletedAt === null; Try / catch
try {
await sessionModel.create(state, true);
} catch (e) {
if (e instanceof UserFriendlyError && e.code === 'copilot_session_deleted') {
state.sessionId = undefined;
await sessionModel.create(state, true);
return;
}
throw e;
} Prevention
- Evict cached sessionIds when the user deletes a chat.
- Check deletedAt before attempting reuseChat=true.
- On copilot_session_deleted, recreate the session and retry.
When it happens
Trigger: Calling find(state) — typically from create(state, reuseChat=true) at copilot-session.ts:380 — when the only matching session has been soft-deleted (deletedAt set). The query selects id and deletedAt; if deletedAt is truthy, reuse is refused.
Common situations: User deleted a chat session in the UI, then the client (still holding the prior state) tries to reuse it instead of creating a new one; race between delete and the next message send; reuseChat=true passed after a prior cleanup job soft-deleted old sessions.
Related errors
- copilot_prompt_invalid
- copilot_session_not_found
- copilot_session_invalid_input
- authentication_required
- authentication_required
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/f1baf6b6ada5ef3d.
Report an issue: GitHub.