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

  1. Serialize uploads per workspace on the client - finish one request before starting the next
  2. Retry after a short delay; the lock is released when the in-flight upload completes
  3. 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

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


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/9b9544b86177c9cd. Report an issue: GitHub.