{"record":{"id":"646dff844f0c2be8","repo":"toeverything/AFFiNE","slug":"blob-not-found-646dff","errorCode":"blob_not_found","errorMessage":"Blob ${blobId} not found in Space ${spaceId}.","messagePattern":"Blob (.+?) not found in Space (.+?)\\.","errorType":"exception","errorClass":"BlobNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/blob.ts","lineNumber":386,"sourceCode":"    @CurrentUser() user: CurrentUser,\n    @Args('workspaceId') workspaceId: string,\n    @Args('key') key: string,\n    @Args('uploadId', { nullable: true }) uploadId?: string,\n    @Args({\n      name: 'parts',\n      type: () => [BlobUploadPartInput],\n      nullable: true,\n    })\n    parts?: BlobUploadPartInput[]\n  ): Promise<string> {\n    await this.ac\n      .user(user.id)\n      .workspace(workspaceId)\n      .assert('Workspace.Blobs.Upload');\n\n    const record = await this.models.blob.get(workspaceId, key);\n    if (!record) {\n      throw new BlobNotFound({ spaceId: workspaceId, blobId: key });\n    }\n    if (record.status === 'completed') {\n      return key;\n    }\n\n    const hasMultipartInput =\n      uploadId !== undefined || (parts?.length ?? 0) > 0;\n    const hasMultipartRecord = !!record.uploadId;\n    if (hasMultipartRecord) {\n      if (!uploadId || !parts || parts.length === 0) {\n        throw new BlobInvalid(\n          'Multipart upload requires both uploadId and parts'\n        );\n      }\n      if (uploadId !== record.uploadId) {\n        throw new BlobInvalid('Upload id mismatch');\n      }\n","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/workspaces/resolvers/blob.ts#L368-L404","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait completeBlobUpload(wsId, key, { uploadId, parts }); // record missing → BlobNotFound\n// after\nlet record = await getBlobRecord(wsId, key);\nif (!record) {\n  const init = await createBlobUpload(wsId, key, size, mime);\n  record = await uploadParts(init);\n}\nawait completeBlobUpload(wsId, key, { uploadId: record.uploadId, parts: record.parts });","handlingStrategy":"validation","validationCode":"// enforce the upload state machine: init must precede complete\nlet record = await getBlobRecord(wsId, key);\nif (!record) {\n  const init = await createBlobUpload(wsId, key, size, mime);\n  record = await persistAndUpload(init); // upload parts, save session state\n}\nreturn completeBlobUpload(wsId, key, { uploadId: record.uploadId, parts: record.parts });","typeGuard":"function isBlobNotFound(e: unknown): e is { code: 'blob_not_found'; spaceId: string; blobId: string } {\n  return typeof e === 'object' && e !== null && (e as any).code === 'blob_not_found';\n}","tryCatchPattern":"try {\n  return await completeBlobUpload(wsId, key, { uploadId, parts });\n} catch (e) {\n  if (isBlobNotFound(e)) {\n    return restartUploadFlow(wsId, key); // session gone: createBlobUpload → parts → complete\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["upload","not-found","blob","state-machine"],"backgroundTag":"resource-not-found","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}