toeverything/AFFiNE · error · CopilotSessionDeleted
copilot_session_deleted
copilot_session_deleted
Error message
Copilot session has been deleted.
What it means
CopilotSessionDeleted, thrown by CopilotSessionModel.find(). find() looks for an aiSession matching userId/workspaceId/docId with parentSessionId null and no action prompt, deliberately selecting deletedAt. When a match exists but deletedAt is set (the row was soft-deleted - cleanup() sets deletedAt = new Date()), the error is thrown so the reuse path in create(state, reuseChat = true) cannot resurrect a deleted conversation.
Solutions
- Catch CopilotSessionDeleted and call create(state, false) to start a fresh session
- Refresh the session list after cleanup/delete actions so the client stops asking to reuse deleted sessions
- If the soft-deleted row is stale data, clear deletedAt (or hard-delete the row) in ai_session
Example fix
// before
const id = await sessions.create(state, true); // throws copilot_session_deleted
// after
let id: string;
try {
id = await sessions.create(state, true);
} catch (e) {
if (e instanceof CopilotSessionDeleted) id = await sessions.create(state, false);
else throw e;
} Defensive patterns
Strategy: try-catch
Try / catch
let sessionId: string;
try {
sessionId = await sessions.create(state, true);
} catch (e) {
if (e instanceof CopilotSessionDeleted) {
sessionId = await sessions.create(state, false); // fall back to a fresh session
} else {
throw e;
}
} Prevention
- Treat CopilotSessionDeleted as 'reuse impossible', not as a hard failure
- Refresh the session list after cleanup/delete actions
- Do not cache a single session id per doc beyond the session's lifetime
When it happens
Trigger: create(state, true) reaches find(state) and the matching aiSession row has a non-null deletedAt - e.g. the user previously cleaned up the chat for that doc and the client retries 'continue chat' with the reuse flag.
Common situations: Stale client state after the user deletes a conversation; cleanup jobs soft-deleting sessions while cached UI still points at them; retrying a create-with-reuse call after the earlier session was removed.
Related errors
- copilot_prompt_invalid
- copilot_session_invalid_input
- copilot_session_not_found
- INVALID_DELEGATED_EDITOR_SESSION
- Session not found
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/f1baf6b6ada5ef3d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/models/copilot-session.ts:529
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,View on GitHub (pinned to 2af30773ae)