paperclipai/paperclip · error · GitHubAttachmentUnavailableError
github_attachment_canonical_image_without_source_anchor
github_attachment_canonical_image_without_source_anchor
Error message
github_attachment_canonical_image_without_source_anchor
What it means
GitHubAttachmentUnavailableError with code github_attachment_canonical_image_without_source_anchor is thrown when no valid candidate anchor was found, but the HTML does contain at least one img whose src is a valid signed same-asset image. That means the signed image exists in the rendering but is not wrapped by an anchor matching the original locator URL — the expected anchor-to-image mapping is broken.
Source
Thrown at server/src/services/chat-github-attachments.ts:665
candidates.push(target);
}
const sameAssetImages = [...fragment.querySelectorAll("img[src]")].filter(
(image) =>
image.getAttribute("src") === locator.url ||
sameAssetImage(image.getAttribute("src")!),
);
if (candidates.length > 1 || sameAssetImages.length > 1)
throw new GitHubAttachmentUnavailableError(
"github_attachment_canonical_mapping_ambiguous",
);
if (candidates.length === 1 && sameAssetImages.length === 1)
return candidates[0]!;
if (
[...fragment.querySelectorAll("img[src]")].some((image) =>
signedImage(image.getAttribute("src")!),
)
)
throw new GitHubAttachmentUnavailableError(
"github_attachment_canonical_image_without_source_anchor",
);
throw new GitHubAttachmentUnavailableError(
"github_attachment_canonical_anchor_missing",
);
}
function imageSignatureMatches(body: Buffer, mime: string): boolean {
if (mime === "image/png")
return body
.subarray(0, 8)
.equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
if (mime === "image/jpeg" || mime === "image/jpg")
return body[0] === 255 && body[1] === 216 && body[2] === 255;
if (mime === "image/gif")
return /^(GIF87a|GIF89a)$/.test(body.subarray(0, 6).toString("ascii"));
if (mime === "image/webp")
return (View on GitHub (pinned to 01ad858492)
Solutions
- Restore the standard GitHub image-anchor form in the comment: anchor href = original /assets/ URL wrapping the image.
- Re-record the attachment locator against the comment's current body/SHA-256 after any edit.
- If the signed URL was pasted manually, replace it with the original github.com/user-attachments/assets/<id> URL.
Example fix
// before <img src="https://private-user-images.../1-abc.png?jwt=..."> // after <a href="https://github.com/user-attachments/assets/abc"><img src="https://private-user-images.../1-abc.png?jwt=..."></a>
Defensive patterns
Strategy: try-catch
Validate before calling
// markdown-level pre-check: image should appear in GitHub anchor form [](assets-url)
const anchorForm = new RegExp(`\\[!\\[[^\\]]*\\]\\(${escapedAssetUrl}\\)\\]\\(${escapedAssetUrl}\\)`);
if (!anchorForm.test(commentBody)) console.warn('image not in standard anchor form'); Try / catch
const target = resolveGitHubCommentAttachmentTarget(attachment, row);
if (target === null) {
// bare signed img without source anchor: re-record locator against current body
await rederiveLocator(attachment);
} Prevention
- Keep GitHub's default anchor rendering; don't hand-edit to bare signed URLs.
- Re-record the locator after any comment edit.
- Preserve the original /assets/ URL as the anchor href.
- Detect and normalize manually pasted signed URLs at ingestion.
When it happens
Trigger: The comment's rendered HTML contains a bare signed <img> (not inside an <a href> matching the locator URL), e.g. markdown that inlines the signed URL directly instead of the original /assets/ link, or the original anchor was edited away while the signed img remains.
Common situations: Users replacing the markdown link form [](assets-url) with a direct signed-URL image; GitHub caching mixing old/new render forms; comment edits removing the anchor while keeping the image.
Related errors
- github_attachment_canonical_anchor_missing
- github_attachment_canonical_image_count_invalid
- github_attachment_canonical_html_unavailable
- github_attachment_canonical_file_unsupported
- github_attachment_canonical_target_denied
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/7a05668c906af7d5.
Report an issue: GitHub.