toeverything/AFFiNE · error · BlobNotFound

blob_not_found

blob_not_found

Error message

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

What it means

Thrown by the copilot blob endpoint when the requested blob cannot be served: there is no signed redirect URL and the storage backend returned no body for the workspace+key. BlobNotFound carries the spaceId (workspaceId) and blobId (key), and indicates the object genuinely does not exist in storage — deleted, never uploaded, or referenced by a stale id.

Source

Thrown at packages/backend/server/src/plugins/copilot/controller.ts:357

    @Res() res: Response,
    @Param('userId') userId: string,
    @Param('workspaceId') workspaceId: string,
    @Param('key') key: string
  ) {
    const { body, metadata, redirectUrl } = await this.storage.get(
      userId,
      workspaceId,
      key,
      true
    );

    if (redirectUrl) {
      // redirect to signed url
      return res.redirect(redirectUrl);
    }

    if (!body) {
      throw new BlobNotFound({
        spaceId: workspaceId,
        blobId: key,
      });
    }

    // metadata should always exists if body is not null
    if (metadata) {
      res.setHeader('content-type', metadata.contentType);
      res.setHeader('last-modified', metadata.lastModified.toUTCString());
      res.setHeader('content-length', metadata.contentLength);
    } else {
      this.logger.warn(`Blob ${workspaceId}/${key} has no metadata`);
    }
    applyAttachHeaders(res, {
      contentType: metadata?.contentType,
      filename: key,
    });

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Confirm the blob key exists in the backing storage for that exact workspace prefix (S3 ls / storage console).
  2. If blobs were deleted, re-trigger generation/upload or purge the referencing message/doc so clients stop requesting dead keys.
  3. Check storage lifecycle rules and exclude the workspace blob prefix from expiry.
  4. For migration issues, re-run the blob migration/sync so all referenced keys exist in the new backend.

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

// Before rendering, HEAD-check the blob URL exists
const ok = await fetch(blobUrl, { method: 'HEAD' }).then(r => r.ok).catch(() => false);
if (!ok) renderPlaceholder();

Type guard

const isBlobNotFound = (e: unknown): boolean =>
  !!e && typeof e === 'object' && (e as any).code === 'blob_not_found';

Try / catch

try {
  const blob = await copilot.getBlob(workspaceId, key);
} catch (e) {
  if (isBlobNotFound(e)) {
    renderPlaceholder({ blobId: e.extensions?.blobId, spaceId: e.extensions?.spaceId });
  } else throw e;
}

Prevention

When it happens

Trigger: GET of a blob/COP file whose key was deleted (workspace cleanup, blob GC) while a message or doc still references it; client builds the URL from an old blob id after the provider response changed; upload to storage succeeded partially (body missing) or the storage bucket was wiped; wrong workspace id in the URL so the lookup targets another space's namespace.

Common situations: Chat history rendering old AI images whose underlying blobs were garbage-collected; S3-compatible storage with lifecycle rules deleting objects; migrations between storage backends that dropped objects; race where the blob URL is requested before the upload committed.

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@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/4aa37ff9c75331de. Report an issue: GitHub.