toeverything/AFFiNE · error · BlobNotFound

blob_not_found

blob_not_found

Error message

Blob ${blobId} not found in Space ${spaceId}.

What it means

Thrown by BlobResolver.completeBlobUpload when models.blob.get(workspaceId, key) finds no record — you are finalizing an upload session that was never initialized (or whose record was deleted). Completion requires the state createBlobUpload created; the record is the source of truth for uploadId/status.

Solutions

  1. Always run createBlobUpload first and only complete after a successful init for the same key
  2. On this error, restart the whole flow: createBlobUpload → upload parts → completeBlobUpload
  3. Persist the session (key, uploadId, parts) so refreshes resume instead of completing blind
  4. If you believe the session should exist, inspect the blob records table for the key

Example fix

// before
await completeBlobUpload(wsId, key, { uploadId, parts }); // record missing → BlobNotFound
// after
let record = await getBlobRecord(wsId, key);
if (!record) {
  const init = await createBlobUpload(wsId, key, size, mime);
  record = await uploadParts(init);
}
await completeBlobUpload(wsId, key, { uploadId: record.uploadId, parts: record.parts });
Defensive patterns

Strategy: validation

Validate before calling

// enforce the upload state machine: init must precede complete
let record = await getBlobRecord(wsId, key);
if (!record) {
  const init = await createBlobUpload(wsId, key, size, mime);
  record = await persistAndUpload(init); // upload parts, save session state
}
return completeBlobUpload(wsId, key, { uploadId: record.uploadId, parts: record.parts });

Type guard

function isBlobNotFound(e: unknown): e is { code: 'blob_not_found'; spaceId: string; blobId: string } {
  return typeof e === 'object' && e !== null && (e as any).code === 'blob_not_found';
}

Try / catch

try {
  return await completeBlobUpload(wsId, key, { uploadId, parts });
} catch (e) {
  if (isBlobNotFound(e)) {
    return restartUploadFlow(wsId, key); // session gone: createBlobUpload → parts → complete
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling completeBlobUpload without a prior successful createBlobUpload for that key; the record removed after a completed/cleaned session; the database wiped or reset between init and complete.

Common situations: Client state lost on page refresh between init and complete; retrying finalize long after the session expired or was finalized and cleaned; init actually failed but the error was swallowed so only complete runs.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18). Data as JSON: /api/errors/646dff844f0c2be8. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/workspaces/resolvers/blob.ts:386

    @CurrentUser() user: CurrentUser,
    @Args('workspaceId') workspaceId: string,
    @Args('key') key: string,
    @Args('uploadId', { nullable: true }) uploadId?: string,
    @Args({
      name: 'parts',
      type: () => [BlobUploadPartInput],
      nullable: true,
    })
    parts?: BlobUploadPartInput[]
  ): Promise<string> {
    await this.ac
      .user(user.id)
      .workspace(workspaceId)
      .assert('Workspace.Blobs.Upload');

    const record = await this.models.blob.get(workspaceId, key);
    if (!record) {
      throw new BlobNotFound({ spaceId: workspaceId, blobId: key });
    }
    if (record.status === 'completed') {
      return key;
    }

    const hasMultipartInput =
      uploadId !== undefined || (parts?.length ?? 0) > 0;
    const hasMultipartRecord = !!record.uploadId;
    if (hasMultipartRecord) {
      if (!uploadId || !parts || parts.length === 0) {
        throw new BlobInvalid(
          'Multipart upload requires both uploadId and parts'
        );
      }
      if (uploadId !== record.uploadId) {
        throw new BlobInvalid('Upload id mismatch');
      }

View on GitHub (pinned to 2af30773ae)