{"record":{"id":"095f0cd1e6b72539","repo":"toeverything/AFFiNE","slug":"blob-quota-exceeded","errorCode":"blob_quota_exceeded","errorMessage":"You have exceeded your blob size quota.","messagePattern":"You have exceeded your blob size quota\\.","errorType":"exception","errorClass":"BlobQuotaExceeded","httpStatus":402,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/blob.ts","lineNumber":188,"sourceCode":"\n  @Mutation(() => String)\n  async setBlob(\n    @CurrentUser() user: CurrentUser,\n    @Args('workspaceId') workspaceId: string,\n    @Args({ name: 'blob', type: () => GraphQLUpload })\n    blob: FileUpload\n  ) {\n    await this.ac\n      .user(user.id)\n      .workspace(workspaceId)\n      .assert('Workspace.Blobs.Write');\n\n    const checkExceeded =\n      await this.quota.getWorkspaceQuotaCalculator(workspaceId);\n\n    let result = checkExceeded(0);\n    if (result?.blobQuotaExceeded) {\n      throw new BlobQuotaExceeded();\n    } else if (result?.storageQuotaExceeded) {\n      throw new StorageQuotaExceeded();\n    }\n\n    const buffer = await readBuffer(blob.createReadStream(), checkExceeded);\n\n    await this.storage.put(workspaceId, blob.filename, buffer);\n    return blob.filename;\n  }\n\n  @Mutation(() => BlobUploadInit)\n  async createBlobUpload(\n    @CurrentUser() user: CurrentUser,\n    @Args('workspaceId') workspaceId: string,\n    @Args('key') key: string,\n    @Args('size', { type: () => Int }) size: number,\n    @Args('mime') mime: string\n  ): Promise<BlobUploadInit> {","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/workspaces/resolvers/blob.ts#L170-L206","documentation":"Thrown by BlobResolver.setBlob when the workspace quota calculator evaluated at zero additional bytes (checkExceeded(0)) already reports blobQuotaExceeded — the workspace is over its per-plan blob quota before this upload is even counted. Distinct from StorageQuotaExceeded (total bytes) one line below; this one tracks the blob-count/size limit of the plan.","triggerScenarios":"GraphQL setBlob (single-shot blob upload) on a workspace whose plan blob limit is already exceeded — the pre-check at size 0 fails immediately, before the stream is read.","commonSituations":"Free/low-tier workspaces after bulk image imports; plan downgrade leaving usage above the new limit; accumulated orphan blobs never garbage-collected.","solutions":["Free up blobs: delete unused blobs/docs so GC reclaims quota","Upgrade the workspace plan to raise the blob quota","Query the workspace quota before uploading and gate the UI with a quota indicator","Reduce blob churn (dedupe, compress) if the workspace legitimately needs many blobs"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// check quota before uploading\nconst quota = await getWorkspaceQuota(wsId); // e.g. { blobQuota, blobUsage, storageQuota, storageUsage }\nif (quota.blobUsage >= quota.blobQuota) {\n  return showBlobQuotaExceededUI(quota);\n}\nawait setBlob(wsId, file);","typeGuard":"function isBlobQuotaExceeded(e: unknown): e is { code: 'blob_quota_exceeded' } {\n  return typeof e === 'object' && e !== null && (e as any).code === 'blob_quota_exceeded';\n}","tryCatchPattern":"try {\n  return await setBlob(wsId, file);\n} catch (e) {\n  if (isBlobQuotaExceeded(e)) {\n    return promptUpgradeOrCleanup('blob'); // won't resolve by retrying the same upload\n  }\n  throw e;\n}","preventionTips":["Surface a live quota indicator in upload UIs","Run blob cleanup (delete unused blobs) before bulk imports","Gate uploads client-side when usage is near quota","Track per-plan limits so downgrades don't surprise users mid-upload"],"tags":["quota","blob","plan-limits","upload"],"backgroundTag":"blob-quota-exceeded","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}