toeverything/AFFiNE · warning · TooManyRequest
too_many_request
too_many_request
Error message
Server is busy
What it means
addWorkspaceArtifact serializes work per workspace with a mutex keyed copilot-locker:workspace:<workspaceId>. If another in-flight request already holds the lock, mutex.acquire returns a falsy lock and the resolver throws TooManyRequest('Server is busy') (code too_many_request) instead of queueing.
Source
Thrown at packages/backend/server/src/plugins/copilot/workspace/resolver.ts:175
@CurrentUser() user: CurrentUser,
@Args('workspaceId', { type: () => String })
workspaceId: string,
@Args({ name: 'blob', type: () => GraphQLUpload })
content: FileUpload
): Promise<CopilotWorkspaceArtifactType> {
await this.ac
.user(user.id)
.workspace(workspaceId)
.assert('Workspace.Settings.Update');
if (!this.copilotWorkspace.canEmbedding) {
throw new CopilotEmbeddingUnavailable();
}
const lockFlag = `${COPILOT_LOCKER}:workspace:${workspaceId}`;
await using lock = await this.mutex.acquire(lockFlag);
if (!lock) {
throw new TooManyRequest('Server is busy');
}
const length = Number(ctx.req.headers['content-length']);
if (length && length >= MAX_EMBEDDABLE_SIZE) {
throw new BlobQuotaExceeded();
}
try {
return await this.copilotWorkspace.addArtifact(workspaceId, content);
} catch (e) {
// passthrough user friendly error
if (e instanceof UserFriendlyError) {
throw e;
}
throw new CopilotFailedToAddWorkspaceArtifact({
message: e instanceof Error ? e.message : String(e),
});
}View on GitHub (pinned to b4c8548c09)
Solutions
- Serialize uploads per workspace on the client - finish one request before starting the next
- Retry after a short delay; the lock is released when the in-flight upload completes
- Disable the submit control while a mutation is pending to prevent double submits
Example fix
// before
artifacts.forEach(a => mutate({ workspaceId, blob: a })); // parallel -> 429
// after
for (const a of artifacts) {
await mutate({ workspaceId, blob: a }); // one at a time per workspace
} Defensive patterns
Strategy: retry
Try / catch
for (let attempt = 1; attempt <= 5; attempt++) {
try {
return await mutate({ workspaceId, blob });
} catch (e) {
if (e?.code !== 'too_many_request' || attempt === 5) throw e;
await sleep(2 ** attempt * 250); // wait for the in-flight upload to finish
}
} Prevention
- Serialize artifact uploads per workspace client-side; one request at a time
- Do not retry uploads immediately on timeout - the original may still hold the lock
- Disable the upload control while a mutation is pending to avoid double submits
When it happens
Trigger: Two concurrent addWorkspaceArtifact mutations for the same workspace: double-clicked upload, multiple tabs uploading, or a client retry overlapping the original slow request (large file embedding takes a while).
Common situations: Aggressive client retry on timeout while the first upload is still processing; batch scripts pushing many artifacts into one workspace in parallel; upload button not disabled during flight.
Related errors
- too_many_request
- invalid_license_to_activate
- internal_server_error
- image_format_not_supported
- copilot_quota_exceeded
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/9b9544b86177c9cd.
Report an issue: GitHub.