{"record":{"id":"133bd08e016b44e3","repo":"toeverything/AFFiNE","slug":"session-not-found-133bd0","errorCode":null,"errorMessage":"Session not found","messagePattern":"Session not found","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/plugins/copilot/resolver.ts","lineNumber":443,"sourceCode":"        .allowLocal()\n        .assert('Workspace.Copilot');\n    }\n    return { userId: user.id, workspaceId, docId: docId || undefined };\n  }\n\n  @ResolveField(() => CopilotSessionType, {\n    description: 'Get the session by id',\n    complexity: 2,\n  })\n  async session(\n    @Parent() copilot: CopilotType,\n    @CurrentUser() user: CurrentUser,\n    @Args('sessionId') sessionId: string\n  ): Promise<CopilotSessionType> {\n    await this.assertPermission(user, copilot);\n    const state = await this.chatSession.getMetaState(sessionId);\n    if (!state) {\n      throw new NotFoundException('Session not found');\n    }\n\n    const projected = this.historyProjector.projectSession(state, {\n      requestUserId: user.id,\n      skipVisibilityFilter: true,\n    });\n    if (!projected) {\n      throw new NotFoundException('Session not found');\n    }\n\n    return this.transformToSessionType(projected);\n  }\n\n  @ResolveField(() => [CopilotSessionType], {\n    description: 'Get the session list in the workspace',\n    deprecationReason: 'use `chats` instead',\n    complexity: 2,\n  })","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/resolver.ts#L425-L461","documentation":"NotFoundException('Session not found') from the copilot.session GraphQL field: after assertPermission passes, chatSession.getMetaState(sessionId) returned no state — the session id does not exist (or its projection state was never built/purged). Distinct from the ownership check in inbox.ts: here permission was already established for the workspace/doc, and the failure is purely data lookup.","triggerScenarios":"Querying copilot session with a deleted/never-existing sessionId; session state evicted by retention/GC; id copied with extra characters; race where the session list showed an entry that was deleted before the detail query.","commonSituations":"Stale session list in the UI after deletion in another tab; deep link to a removed session; migration dropped old session state.","solutions":["Re-query the session list and drop ids that 404, then let the user pick a live session","For deep links, catch the NotFoundException and redirect to the session list","Verify the sessionId string is passed unchanged (no trimming/encoding artifacts)"],"exampleFix":"# before\nquery { copilot { session(sessionId: $staleId) { id messages } } }\n\n# after\n# client: on SESSION_NOT_FOUND error, refetch list and navigate\ntry {\n  const s = await fetchSession(sessionId);\n} catch (e) {\n  if (e.code === 'NOT_FOUND') router.replace('/copilot/chats');\n}","handlingStrategy":"try-catch","validationCode":"const state = await chatSession.getMetaState(sessionId);\nif (!state) {\n  await refreshSessionList();\n  throw new Error('Session no longer exists');\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await fetchCopilotSession(copilot, sessionId);\n} catch (e) {\n  if (e instanceof NotFoundException && /Session not found/.test(e.message)) {\n    removeSessionFromCache(sessionId);\n    navigateTo('/copilot/chats');\n  } else throw e;\n}","preventionTips":["Purge session ids from local cache on delete events","Deep links should tolerate 404s by falling back to the session list","Pass session ids through untouched — no trimming or re-encoding"],"tags":["copilot","graphql","session","not-found"],"backgroundTag":"session-not-found","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}