{"record":{"id":"e76d86aebdededac","repo":"paperclipai/paperclip","slug":"github-attachment-canonical-html-unavailable","errorCode":"github_attachment_canonical_html_unavailable","errorMessage":"github_attachment_canonical_html_unavailable","messagePattern":"github_attachment_canonical_html_unavailable","errorType":"error_code","errorClass":"GitHubAttachmentUnavailableError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-github-attachments.ts","lineNumber":572,"sourceCode":"  if (\n    String(row.id) !== locator.sourceMessageId ||\n    typeof row.url !== \"string\" ||\n    row.url.toLowerCase() !== request.url.toLowerCase()\n  )\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_canonical_source_mismatch\",\n    );\n  if (\n    typeof row.body !== \"string\" ||\n    row.body.length > 200_000 ||\n    createHash(\"sha256\").update(row.body).digest(\"hex\") !==\n      locator.sourceBodySha256\n  )\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_canonical_body_mismatch\",\n    );\n  if (typeof row.body_html !== \"string\" || row.body_html.length > 600_000)\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_canonical_html_unavailable\",\n    );\n  if (thread[4]) {\n    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    );","sourceCodeStart":554,"sourceCodeEnd":590,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-github-attachments.ts#L554-L590","documentation":"GitHubAttachmentUnavailableError with code github_attachment_canonical_html_unavailable is thrown by resolveCanonicalAttachmentTargetOrThrow in chat-github-attachments.ts when the GitHub API comment/issue response row's body_html field is not a string or exceeds 600,000 characters. The service relies on GitHub's server-rendered HTML to locate the signed image anchor; without usable body_html it cannot safely resolve a canonical download target. This is a deliberate guard, not a network failure.","triggerScenarios":"Calling resolveGitHubCommentAttachmentTarget/prepareGitHubPublicAttachment for a GitHub comment whose API response has body_html missing or null (e.g. mime_type requested as text only), or a comment whose rendered HTML exceeds the 600KB cap (very large comment bodies).","commonSituations":"Resolver responses built from an API client configured without 'application/vnd.github.html+json' or without full JSON rendering; extremely long issue comments with many embedded images; mocked/stubbed GitHub API fixtures that omit body_html; GitHub changing the render payload shape.","solutions":["Ensure the comment resolver fetches the GitHub API row with rendered HTML included (default JSON representation returns body_html; do not pass Accept headers that strip it).","Verify the resolver passes the raw API row object into resolveGitHubCommentAttachmentTarget, not a projection that drops body_html.","If the comment is genuinely huge (>600KB HTML), reduce the comment content on GitHub or split the attachment into a smaller comment; the cap is intentional.","Check fixtures/tests supply a realistic body_html string."],"exampleFix":"// before\nresolveGitHubCommentAttachmentTarget(attachment, { id: row.id, body: row.body });\n// after\nresolveGitHubCommentAttachmentTarget(attachment, row); // keep full API row incl. body_html","handlingStrategy":"validation","validationCode":"function hasRenderableHtml(row: unknown): boolean {\n  return typeof row === 'object' && row !== null && 'body_html' in row &&\n    typeof (row as any).body_html === 'string' && (row as any).body_html.length <= 600_000;\n}\nif (!hasRenderableHtml(apiRow)) throw new Error('resolver response lacks usable body_html');","typeGuard":"const isRowWithHtml = (v: unknown): v is { body_html: string; [k: string]: unknown } =>\n  typeof v === 'object' && v !== null && typeof (v as any).body_html === 'string';","tryCatchPattern":"try {\n  const target = resolveGitHubCommentAttachmentTarget(attachment, row);\n  if (target === null) skipCanonicalPath(attachment);\n} catch (e) { if (e instanceof GitHubAttachmentUnavailableError) fallback(attachment); else throw e; }","preventionTips":["Fetch GitHub rows with the default JSON representation so body_html is present.","Don't project/strip fields from the API row before resolving.","Watch for comments near the 600KB HTML cap and split large comments.","Keep test fixtures realistic, including body_html."],"tags":["github","attachments","html-rendering","guard"],"backgroundTag":"unexpected-api-response-shape","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"}