toeverything/AFFiNE · error · NotFoundException
Workspace not found
Error message
Workspace not found
What it means
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.
Source
Thrown at packages/backend/server/src/plugins/copilot/resolver.ts:413
}
@ResolveField(() => CopilotQuotaType, {
name: 'quota',
description: 'Get the quota of the user in the workspace',
complexity: 2,
})
async getQuota(@CurrentUser() user: CurrentUser): Promise<CopilotQuotaType> {
return await this.chatSession.getQuota(user.id);
}
private async assertPermission(
user: CurrentUser,
options: { workspaceId?: string | null; docId?: string | null },
fallbackAction?: DocAction
) {
const { workspaceId, docId } = options;
if (!workspaceId) {
throw new NotFoundException('Workspace not found');
}
if (docId) {
await this.ac
.user(user.id)
.doc({ workspaceId, docId })
.allowLocal()
.assert(fallbackAction ?? 'Doc.Update');
} else {
await this.ac
.user(user.id)
.workspace(workspaceId)
.allowLocal()
.assert('Workspace.Copilot');
}
return { userId: user.id, workspaceId, docId: docId || undefined };
}
@ResolveField(() => CopilotSessionType, {View on GitHub (pinned to b4c8548c09)
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
Example fix
# before
query { copilot { session(sessionId: "...") { id } } } # copilot.workspaceId null -> 404
# after
query {
workspace(id: $wsId) {
copilot { session(sessionId: "...") { id } }
}
} Defensive patterns
Strategy: validation
Validate before calling
if (!copilot.workspaceId) {
// no server workspace context; skip the query or resolve one first
throw new Error('This view requires a cloud workspace');
}
await graphql(copilotSessionQuery, { workspaceId: copilot.workspaceId }); Type guard
const hasWorkspaceContext = (
c: { workspaceId?: string | null } | null | undefined
): c is { workspaceId: string } => typeof c?.workspaceId === 'string' && c.workspaceId.length > 0; Try / catch
try {
const session = await fetchCopilotSession(copilot, sessionId);
} catch (e) {
if (e instanceof NotFoundException && /Workspace not found/.test(e.message)) {
redirectToListOrEnableCloudSync();
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- space_not_found
- Session not found
- copilot_session_not_found
- copilot_transcription_job_not_found
- user_not_found
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/4c6852d64fce47de.
Report an issue: GitHub.