{"record":{"id":"12b6fab7e7939aeb","repo":"paperclipai/paperclip","slug":"github-attachment-canonical-file-unsupported","errorCode":"github_attachment_canonical_file_unsupported","errorMessage":"github_attachment_canonical_file_unsupported","messagePattern":"github_attachment_canonical_file_unsupported","errorType":"error_code","errorClass":"GitHubAttachmentUnavailableError","httpStatus":null,"severity":"warning","filePath":"server/src/services/chat-github-attachments.ts","lineNumber":594,"sourceCode":"    if (\n      row.pull_request_url !==\n        `https://api.github.com/repos/${thread[1]}/pulls/${thread[3]}` ||\n      String(row.in_reply_to_id ?? row.id) !== thread[4]\n    )\n      throw new GitHubAttachmentUnavailableError(\n        \"github_attachment_canonical_source_mismatch\",\n      );\n  } else if (\n    row.issue_url !==\n    `https://api.github.com/repos/${thread[1]}/issues/${thread[3]}`\n  )\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_canonical_source_mismatch\",\n    );\n  const assetId = /\\/assets\\/([a-f0-9-]+)$/i.exec(locator.url)?.[1];\n  // Generic private files have no documented signed-download representation.\n  if (!assetId)\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_canonical_file_unsupported\",\n    );\n  const sourceBody = row.body;\n  const imagePath = new RegExp(\n    `^/[1-9][0-9]*/[1-9][0-9]*-${assetId}\\\\.(?:png|jpe?g|gif|webp)$`,\n    \"i\",\n  );\n  const sameAssetImage = (src: string): URL | null => {\n    const target = allowedRedirect(src, locator.url);\n    return target?.hostname === \"private-user-images.githubusercontent.com\" &&\n      imagePath.test(target.pathname)\n      ? target\n      : null;\n  };\n  const signedImage = (src: string): URL | null => {\n    const target = sameAssetImage(src);\n    return target &&\n      [...target.searchParams.keys()].join(\",\") === \"jwt\" &&","sourceCodeStart":576,"sourceCodeEnd":612,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-github-attachments.ts#L576-L612","documentation":"GitHubAttachmentUnavailableError with code github_attachment_canonical_file_unsupported is thrown when the attachment locator URL does not end in /assets/<uuid>, i.e. it is not a GitHub user-upload image asset. Only image assets on GitHub's asset hosts have a documented signed-download representation; generic private files (e.g. repository files or other upload kinds) deliberately cannot be canonicalized to a signed URL.","triggerScenarios":"Calling resolveGitHubCommentAttachmentTarget for an attachment whose locator.url lacks a trailing /assets/<hex-uuid> segment — e.g. github user-attachments URLs of a non-asset form, or arbitrary file links pasted in a comment.","commonSituations":"Attachments created from generic file URLs (PDFs, zips, non-image user-attachments); URLs copied from rendered markdown where GitHub rewrites asset links; attempting canonical resolution for attachment kinds the feature never supported.","solutions":["Only run canonical resolution for image attachments whose URL ends in /assets/<uuid> (png/jpg/gif/webp).","For non-image or generic files, serve them through the direct download path instead of signed-URL canonicalization.","Check how the attachment handle was created; fix upstream URL extraction to preserve the /assets/<uuid> form."],"exampleFix":"// before\nresolveGitHubCommentAttachmentTarget(fileAttachment, row);\n// after\nif (/\\/assets\\/[a-f0-9-]+$/i.test(attachment.url)) resolveGitHubCommentAttachmentTarget(attachment, row);","handlingStrategy":"type-guard","validationCode":"const isImageAsset = (url: string) => /\\/assets\\/[a-f0-9-]+\\.(png|jpe?g|gif|webp)?/i.test(url) || /\\/assets\\/[a-f0-9-]+$/i.test(url);\nif (!isImageAsset(attachment.url)) useDirectDownloadPath(attachment);","typeGuard":"const isCanonicalizable = (a: Attachment): boolean => /\\/assets\\/[a-f0-9-]+$/i.test(a.url);","tryCatchPattern":"try {\n  return await prepareGitHubPublicAttachment(attachment);\n} catch (e) {\n  if (e instanceof GitHubAttachmentUnavailableError && e.code === 'github_attachment_canonical_file_unsupported')\n    return directDownload(attachment);\n  throw e;\n}","preventionTips":["Classify attachments as image-asset vs generic file at ingestion time.","Route only /assets/<uuid> URLs through canonical resolution.","Keep the original user-attachments/assets URL form intact during extraction.","Document that generic private files are unsupported for signed canonicalization."],"tags":["github","attachments","unsupported-operation","images"],"backgroundTag":"unsupported-operation","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}