{"record":{"id":"6ab3b0dfaf620823","repo":"toeverything/AFFiNE","slug":"comment-attachment-not-found","errorCode":"comment_attachment_not_found","errorMessage":"Comment attachment not found.","messagePattern":"Comment attachment not found\\.","errorType":"exception","errorClass":"CommentAttachmentNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/controller.ts","lineNumber":393,"sourceCode":"  @CallMetric('controllers', 'workspace_get_comment_attachment')\n  async commentAttachment(\n    @CurrentUser() user: CurrentUser,\n    @Param('id') workspaceId: string,\n    @Param('docId') docId: string,\n    @Param('key') key: string,\n    @Res() res: Response\n  ) {\n    await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Read');\n\n    const { body, metadata, redirectUrl } =\n      await this.commentAttachmentStorage.get(workspaceId, docId, key, true);\n\n    if (redirectUrl) {\n      return res.redirect(redirectUrl);\n    }\n\n    if (!body) {\n      throw new CommentAttachmentNotFound();\n    }\n\n    // metadata should always exists if body is not null\n    if (metadata) {\n      res.setHeader('content-type', metadata.contentType);\n      res.setHeader('last-modified', metadata.lastModified.toUTCString());\n      res.setHeader('content-length', metadata.contentLength);\n    } else {\n      this.logger.warn(\n        `Comment attachment ${workspaceId}/${docId}/${key} has no metadata`\n      );\n    }\n    applyAttachHeaders(res, {\n      contentType: metadata?.contentType,\n      filename: key,\n    });\n\n    res.setHeader('cache-control', 'private, max-age=2592000, immutable');","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/controller.ts#L375-L411","documentation":"Thrown when commentAttachmentStorage.get returns neither a redirectUrl nor a body for the requested comment attachment key. The attachment object is missing from storage.","triggerScenarios":"Fetching a comment attachment by key when the object was deleted, never fully uploaded, or is not yet replicated.","commonSituations":"Attachment deleted with its comment; upload interrupted; object-store lifecycle removed it; wrong key.","solutions":["Verify the key and that the attachment still exists.","Re-upload the attachment if lost.","Check comment-attachment storage health and lifecycle rules.","Hide broken attachment UI gracefully."],"exampleFix":"// before\nconst { body } = await commentAttachmentStorage.get(ws, docId, key)\nreturn body\n// after\nconst { body, redirectUrl } = await commentAttachmentStorage.get(ws, docId, key, true)\nif (redirectUrl) return res.redirect(redirectUrl)\nif (!body) throw new NotFound('comment attachment missing')","handlingStrategy":"validation","validationCode":"const meta = await commentAttachmentStorage.head(workspaceId, docId, key)\nif (!meta) return brokenAttachmentPlaceholder(key)","typeGuard":null,"tryCatchPattern":"try { await loadCommentAttachment(key) } catch (e) {\n  if (e.code === 'comment_attachment_not_found') showPlaceholder()\n  else throw e\n}","preventionTips":["Track attachment lifecycle with comments.","HEAD before rendering.","Clean up orphaned keys."],"tags":["comments","attachments","not-found","storage"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}