{"record":{"id":"603e2457f3526139","repo":"toeverything/AFFiNE","slug":"blob-not-found","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/controller.ts","lineNumber":137,"sourceCode":"    const { body, metadata, redirectUrl } = await this.storage.get(\n      workspaceId,\n      name,\n      true\n    );\n\n    if (redirectUrl) {\n      // redirect to signed url\n      if (redirect === 'manual') {\n        return res.send({\n          url: redirectUrl,\n        });\n      } else {\n        return res.redirect(redirectUrl);\n      }\n    }\n\n    if (!body) {\n      throw new BlobNotFound({\n        spaceId: workspaceId,\n        blobId: name,\n      });\n    }\n\n    // metadata should always exists if body is not null\n    if (metadata) {\n      res.setHeader(\n        'content-type',\n        metadata.contentType.startsWith('application/json') // application/json is reserved for redirect url\n          ? 'text/json'\n          : metadata.contentType\n      );\n      res.setHeader('last-modified', metadata.lastModified.toUTCString());\n      res.setHeader('content-length', metadata.contentLength);\n    } else {\n      this.logger.warn(`Blob ${workspaceId}/${name} has no metadata`);\n    }","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/workspaces/controller.ts#L119-L155","documentation":"Thrown by the workspace blob route when storage.get(workspaceId, name, true) returns neither a body nor a redirectUrl — the named blob object does not exist in the workspace's blob storage. Permission already passed; this is a pure storage-layer miss.","triggerScenarios":"GET /api/workspaces/:id/blobs/:name with a name never uploaded, deleted, or whose object is missing from the backend even though the URL was well-formed (no signed-url redirect available either).","commonSituations":"Stale blob references in doc content after blob garbage collection; storage bucket wiped or migrated without copying objects; truncated/typo'd blob key; upload recorded in the metadata store but the object write failed.","solutions":["Verify the blob key exists (blobs list API or the storage backend) before linking it","Re-upload the missing blob and update the referencing doc","If objects vanish systematically, audit storage provider config (bucket, prefix) and any GC job","Watch for the route's 'has no metadata' warning in logs — it flags half-written objects"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"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 fetchBlob(wsId, blobKey);\n} catch (e) {\n  if (isBlobNotFound(e)) {\n    return placeholderImage(e.blobId); // media gone — degrade gracefully, no retry\n  }\n  throw e;\n}","preventionTips":["Validate blob keys exist (list API / storage head) before persisting them in doc content","Don't run aggressive GC against blobs still referenced by docs","When migrating storage, verify object counts match the metadata store","Treat upload failures as fatal for the referencing content so half-written blobs aren't linked"],"tags":["not-found","blob","storage"],"backgroundTag":"resource-not-found","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"}