{"record":{"id":"4c6852d64fce47de","repo":"toeverything/AFFiNE","slug":"workspace-not-found","errorCode":null,"errorMessage":"Workspace not found","messagePattern":"Workspace not found","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/plugins/copilot/resolver.ts","lineNumber":413,"sourceCode":"  }\n\n  @ResolveField(() => CopilotQuotaType, {\n    name: 'quota',\n    description: 'Get the quota of the user in the workspace',\n    complexity: 2,\n  })\n  async getQuota(@CurrentUser() user: CurrentUser): Promise<CopilotQuotaType> {\n    return await this.chatSession.getQuota(user.id);\n  }\n\n  private async assertPermission(\n    user: CurrentUser,\n    options: { workspaceId?: string | null; docId?: string | null },\n    fallbackAction?: DocAction\n  ) {\n    const { workspaceId, docId } = options;\n    if (!workspaceId) {\n      throw new NotFoundException('Workspace not found');\n    }\n    if (docId) {\n      await this.ac\n        .user(user.id)\n        .doc({ workspaceId, docId })\n        .allowLocal()\n        .assert(fallbackAction ?? 'Doc.Update');\n    } else {\n      await this.ac\n        .user(user.id)\n        .workspace(workspaceId)\n        .allowLocal()\n        .assert('Workspace.Copilot');\n    }\n    return { userId: user.id, workspaceId, docId: docId || undefined };\n  }\n\n  @ResolveField(() => CopilotSessionType, {","sourceCodeStart":395,"sourceCodeEnd":431,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/resolver.ts#L395-L431","documentation":"NotFoundException('Workspace not found') from CopilotResolver.assertPermission: the helper is invoked with options.workspaceId undefined/null (the copilot parent object carries no workspace context), so there is nothing to permission-check. It is a 404 used to avoid leaking whether any workspace exists — distinct from a permission denial, which would come from the ac.assert chain.","triggerScenarios":"Querying a copilot session/chats field on a CopilotType parent whose workspaceId is null (e.g. a local or account-scoped context); calling resolvers like session()/sessions() without a workspace-bound copilot parent; GraphQL query built with missing workspace context.","commonSituations":"Client builds the copilot query for local workspaces where no server workspaceId exists; stale query shape after API refactor added workspaceId requirement; null propagating from an upstream resolver.","solutions":["Ensure the query provides a copilot parent bound to a server workspaceId (fetch the copilot/workspace object first)","Skip the server-side session queries for local-workspace contexts in the client","If maintaining the API: consider making the missing-workspace case explicit in the schema rather than a 404"],"exampleFix":"# before\nquery { copilot { session(sessionId: \"...\") { id } } } # copilot.workspaceId null -> 404\n\n# after\nquery {\n  workspace(id: $wsId) {\n    copilot { session(sessionId: \"...\") { id } }\n  }\n}","handlingStrategy":"validation","validationCode":"if (!copilot.workspaceId) {\n  // no server workspace context; skip the query or resolve one first\n  throw new Error('This view requires a cloud workspace');\n}\nawait graphql(copilotSessionQuery, { workspaceId: copilot.workspaceId });","typeGuard":"const hasWorkspaceContext = (\n  c: { workspaceId?: string | null } | null | undefined\n): c is { workspaceId: string } => typeof c?.workspaceId === 'string' && c.workspaceId.length > 0;","tryCatchPattern":"try {\n  const session = await fetchCopilotSession(copilot, sessionId);\n} catch (e) {\n  if (e instanceof NotFoundException && /Workspace not found/.test(e.message)) {\n    redirectToListOrEnableCloudSync();\n  } else throw e;\n}","preventionTips":["Query copilot fields through a workspace-bound parent so workspaceId is always present","Suppress copilot server queries for local workspaces in the client","Watch for null workspaceId when composing GraphQL queries dynamically"],"tags":["copilot","graphql","workspace","not-found"],"backgroundTag":"workspace-not-found","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}