{"record":{"id":"f1baf6b6ada5ef3d","repo":"toeverything/AFFiNE","slug":"copilot-session-deleted","errorCode":"copilot_session_deleted","errorMessage":"Copilot session has been deleted.","messagePattern":"Copilot session has been deleted\\.","errorType":"exception","errorClass":"CopilotSessionDeleted","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/models/copilot-session.ts","lineNumber":529,"sourceCode":"    const extraCondition: Record<string, any> = {};\n    if (state.parentSessionId) {\n      // also check session id if provided session is forked session\n      extraCondition.id = state.sessionId;\n      extraCondition.parentSessionId = state.parentSessionId;\n    }\n\n    const session = await this.db.aiSession.findFirst({\n      where: {\n        userId: state.userId,\n        workspaceId: state.workspaceId,\n        docId: state.docId,\n        parentSessionId: null,\n        ...this.noActionPromptCondition(),\n        ...extraCondition,\n      },\n      select: { id: true, deletedAt: true },\n    });\n    if (session?.deletedAt) throw new CopilotSessionDeleted();\n    return session?.id;\n  }\n\n  @Transactional()\n  async getExists<Select extends Prisma.AiSessionSelect>(\n    sessionId: string,\n    select?: Select,\n    where?: Omit<Prisma.AiSessionWhereInput, 'id' | 'deletedAt'>\n  ) {\n    return (await this.db.aiSession.findUnique({\n      where: { ...where, id: sessionId, deletedAt: null },\n      select,\n    })) as Prisma.AiSessionGetPayload<{ select: Select }> | null;\n  }\n\n  @Transactional()\n  async get(\n    sessionId: string,","sourceCodeStart":511,"sourceCodeEnd":547,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/models/copilot-session.ts#L511-L547","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst id = await sessions.create(state, true); // throws copilot_session_deleted\n\n// after\nlet id: string;\ntry {\n  id = await sessions.create(state, true);\n} catch (e) {\n  if (e instanceof CopilotSessionDeleted) id = await sessions.create(state, false);\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let sessionId: string;\ntry {\n  sessionId = await sessions.create(state, true);\n} catch (e) {\n  if (e instanceof CopilotSessionDeleted) {\n    sessionId = await sessions.create(state, false); // fall back to a fresh session\n  } else {\n    throw e;\n  }\n}","preventionTips":["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"],"tags":["copilot","session","soft-delete","reuse"],"backgroundTag":"soft-deleted-record-access","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}