paperclipai/paperclip · error · GitHubAttachmentUnavailableError
github_attachment_empty
github_attachment_empty
Error message
github_attachment_empty
What it means
After fully reading the attachment stream, prepareGitHubPublicAttachment throws GitHubAttachmentUnavailableError with code "github_attachment_empty" at chat-github-attachments.ts:809-810 when zero bytes were received (size === 0). An empty body can never be a valid image/file attachment, so the download is rejected rather than returning a 0-byte attachment.
Source
Thrown at server/src/services/chat-github-attachments.ts:810
try {
for (;;) {
signal.throwIfAborted();
const next = await reader.read();
signal.throwIfAborted();
if (next.done) break;
size += next.value.byteLength;
if (size > MAX_ATTACHMENT_BYTES)
throw new GitHubAttachmentUnavailableError(
"github_attachment_too_large",
);
chunks.push(Buffer.from(next.value));
}
} finally {
signal.removeEventListener("abort", cancel);
await reader.cancel().catch(() => undefined);
}
if (!size)
throw new GitHubAttachmentUnavailableError("github_attachment_empty");
// Content-Length describes compressed bytes when Content-Encoding is present.
if (
declared &&
!response.headers.get("content-encoding") &&
size !== Number(declared)
)
throw new GitHubAttachmentUnavailableError(
"github_attachment_invalid_response",
);
const body = Buffer.concat(chunks, size);
if (
!imageSignatureMatches(body, mimeType) ||
/^\s*(?:<!doctype\s+html|<html\b)/i.test(
body.subarray(0, 512).toString("utf8"),
)
)
throw new GitHubAttachmentUnavailableError(
"github_attachment_invalid_response",View on GitHub (pinned to 01ad858492)
Solutions
- Verify the attachment URL resolves to real content (open it in a browser; it should download, not be 0 bytes)
- Re-upload or regenerate the attachment so a fresh URL with actual bytes exists
- Check proxies/CDNs between the server and GitHub for body-stripping behavior
- If the source is a GitHub comment asset, ensure the canonical resolution found the correct asset URL
Example fix
// before: stub returns 200 with no body
fetchMock.mockResolvedValue(new Response(null, { status: 200 }));
// after: stub returns actual image bytes
fetchMock.mockResolvedValue(new Response(pngBytes, { status: 200, headers: { "content-type": "image/png", "content-length": String(pngBytes.length) } })); Defensive patterns
Strategy: try-catch
Validate before calling
const probe = await fetch(url, { method: "GET", headers: { range: "bytes=0-15" } }); if (probe.status === 200 && (await probe.arrayBuffer()).byteLength === 0) return skip("empty body"); Type guard
null
Try / catch
try { return await prepareGitHubPublicAttachment(attachment, signal); } catch (e) { if (e?.code === "github_attachment_empty") { return unavailableAttachmentCard(attachment); } throw e; } Prevention
- Validate attachment URLs resolve to real bytes before embedding them in chat
- Re-upload assets whose source upload was interrupted
- Watch for CDN/proxy misconfigurations that strip response bodies
- Ensure test doubles return non-empty bodies with 200
When it happens
Trigger: The remote server returns HTTP 200 with a zero-length body, or the stream ends immediately with no chunks, so the size counter stays 0 after the read loop.
Common situations: GitHub media endpoints returning 200 with empty body for deleted/expired assets; misbehaving proxies or CDN edge nodes; aborted-but-not-signalled upstream uploads; testing against a stub server that returns 200 without a body.
Related errors
- github_attachment_invalid_response
- github_attachment_canonical_api_access_denied | github_attac
- github_attachment_canonical_api_too_large
- github_attachment_canonical_html_unavailable
- github_attachment_canonical_file_unsupported
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/6fbeebb1fd7b5f55.
Report an issue: GitHub.