toeverything/AFFiNE · error · AccessDenied
access_denied
access_denied
Error message
You do not have permission to access this resource.
What it means
The artifact retrieval service authorizes the user against the workspace before running embedding match; if authorize(userId, workspaceId) fails it throws AccessDenied (code `access_denied`, status `access_denied`). This gate protects workspace-scoped artifact (embedding) search from users who are not members of the workspace.
Source
Thrown at packages/backend/server/src/plugins/copilot/retrieval/artifact.ts:35
private async authorize(userId: string, workspaceId: string) {
return await this.access
.user(userId)
.workspace(workspaceId)
.allowLocal()
.can('Workspace.Read');
}
async search(options: {
userId: string;
workspaceId: string;
query: string;
retrieval: RuntimeRetrievalScope;
limit: number;
messageId?: string;
signal?: AbortSignal;
}) {
if (!(await this.authorize(options.userId, options.workspaceId))) {
throw new AccessDenied();
}
let degraded = false;
let matched: Awaited<ReturnType<NativeEmbeddingService['match']>> = [];
try {
matched = await this.embedding.match(
options.workspaceId,
options.query,
'artifact',
options.retrieval,
options.limit,
options.signal
);
} catch (error) {
if (options.signal?.aborted) throw error;
degraded = true;
}
const matchedIds = new Set(matched.map(hit => hit.artifactId));
const missingRequired =View on GitHub (pinned to b4c8548c09)
Solutions
- Verify the user is an active member of the workspace with permission to use Copilot/retrieval
- Double-check the workspaceId passed to the retrieval call matches the workspace the artifacts belong to
- Refresh auth/session if membership was recently changed, then retry
- For self-hosted: inspect workspace permissions in the DB to confirm the user-workspace relation exists
Defensive patterns
Strategy: validation
Validate before calling
// guard: confirm workspace access before retrieval search
const access = await checkWorkspacePermission(userId, workspaceId); // your ACL/API check
if (!access.canRead) {
throw new Error('user has no access to workspace — skip artifact search');
}
await artifactSearch({ userId, workspaceId, query, retrieval, limit }); Type guard
function isAccessDenied(e: unknown): boolean {
return (e as { extensions?: { code?: string } })?.extensions?.code === 'access_denied';
} Try / catch
try {
await artifactSearch(params);
} catch (e) {
if (isAccessDenied(e)) {
disableRetrievalForWorkspace(params.workspaceId); // stop retrying with same identity
return [];
}
throw e;
} Prevention
- Derive workspaceId from the same context that authenticated the user, never from user input alone
- Re-check membership when switching workspaces or after permission changes
- Fail retrieval soft (empty results) so a permission gap degrades, not breaks, the chat flow
When it happens
Trigger: Calling retrieval search with a userId that is not a member of workspaceId or lacks the required role; passing the wrong workspaceId (e.g. from another workspace's context); user removed from the workspace while their session/token is still valid.
Common situations: Cross-workspace id mixups in multi-workspace clients; permission revoked mid-session; self-hosted setups where workspace membership sync (e.g. from an identity provider) lags; tests using users never added to the workspace.
Related errors
- copilot_selected_sources_limit_exceeded
- copilot_failed_to_add_workspace_artifact
- space_access_denied
- workspace_permission_not_found
- doc_action_denied
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/747ce8abbc90a671.
Report an issue: GitHub.