toeverything/AFFiNE · error · CommentAttachmentNotFound

comment_attachment_not_found

comment_attachment_not_found

Error message

Comment attachment not found.

What it means

Thrown by the comment-attachment route (GET /:id/docs/:docId/comment-attachments/:key) when commentAttachmentStorage.get(workspaceId, docId, key, true) returns neither a body nor a redirectUrl, after the Doc.Read assert passed. Plain storage-layer miss for comment media.

Solutions

  1. Verify the key against the comment's stored attachment data before fetching
  2. Re-upload the attachment and update the comment if the media matters
  3. Audit comment-attachment storage config if objects vanish systematically
  4. Render a placeholder in the UI on 404 rather than erroring the whole comment thread
Defensive patterns

Strategy: try-catch

Type guard

function isCommentAttachmentNotFound(e: unknown): e is { code: 'comment_attachment_not_found' } {
  return typeof e === 'object' && e !== null && (e as any).code === 'comment_attachment_not_found';
}

Try / catch

try {
  return await fetchCommentAttachment(wsId, docId, key);
} catch (e) {
  if (isCommentAttachmentNotFound(e)) {
    return renderAttachmentPlaceholder(); // keep the comment thread usable
  }
  throw e;
}

Prevention

When it happens

Trigger: Fetching a comment attachment whose key was never uploaded, was removed, or whose object is gone from the storage backend with no signed-url fallback available.

Common situations: Comments referencing deleted attachments; storage migration losing objects; wrong key passed by the client; uploads that failed before the object write but after the comment was created.

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/6ab3b0dfaf620823. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/workspaces/controller.ts:393

  @CallMetric('controllers', 'workspace_get_comment_attachment')
  async commentAttachment(
    @CurrentUser() user: CurrentUser,
    @Param('id') workspaceId: string,
    @Param('docId') docId: string,
    @Param('key') key: string,
    @Res() res: Response
  ) {
    await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Read');

    const { body, metadata, redirectUrl } =
      await this.commentAttachmentStorage.get(workspaceId, docId, key, true);

    if (redirectUrl) {
      return res.redirect(redirectUrl);
    }

    if (!body) {
      throw new CommentAttachmentNotFound();
    }

    // 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(
        `Comment attachment ${workspaceId}/${docId}/${key} has no metadata`
      );
    }
    applyAttachHeaders(res, {
      contentType: metadata?.contentType,
      filename: key,
    });

    res.setHeader('cache-control', 'private, max-age=2592000, immutable');

View on GitHub (pinned to b4c8548c09)