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
- Always run createBlobUpload first and only complete after a successful init for the same key
- On this error, restart the whole flow: createBlobUpload → upload parts → completeBlobUpload
- Persist the session (key, uploadId, parts) so refreshes resume instead of completing blind
- 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
- Never call complete without a successful init for the same key in the same session
- Persist upload session state (key, uploadId, parts) so refreshes resume rather than finalize blind
- Treat init failures as fatal — swallow them and complete will 404 later
- If finalization may happen much later, verify the record still exists first
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)