{"record":{"id":"679b9b6e738595bd","repo":"paperclipai/paperclip","slug":"github-attachment-canonical-api-too-large","errorCode":"github_attachment_canonical_api_too_large","errorMessage":"github_attachment_canonical_api_too_large","messagePattern":"github_attachment_canonical_api_too_large","errorType":"error_code","errorClass":"GitHubAttachmentUnavailableError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-github-attachments.ts","lineNumber":500,"sourceCode":"              : \"github_attachment_canonical_api_invalid_response\",\n      );\n    }\n    const reader = response.body.getReader();\n    const chunks: Uint8Array[] = [];\n    let size = 0;\n    const cancel = () => {\n      void reader.cancel().catch(() => undefined);\n    };\n    signal.addEventListener(\"abort\", cancel, { once: true });\n    try {\n      for (;;) {\n        signal.throwIfAborted();\n        const next = await reader.read();\n        signal.throwIfAborted();\n        if (next.done) break;\n        size += next.value.byteLength;\n        if (size > MAX_COMMENT_RESPONSE_BYTES)\n          throw new GitHubAttachmentUnavailableError(\n            \"github_attachment_canonical_api_too_large\",\n          );\n        chunks.push(next.value);\n      }\n    } finally {\n      signal.removeEventListener(\"abort\", cancel);\n      await reader.cancel().catch(() => undefined);\n    }\n    return new Response(Buffer.concat(chunks), {\n      status: 200,\n      headers: { \"content-type\": \"application/json\" },\n    });\n  };\n}\n\n/**\n * The authenticated rendering is evidence only for this exact unchanged source.\n * We support GitHub's image anchor mapping, not arbitrary HTML URL extraction.","sourceCodeStart":482,"sourceCodeEnd":518,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-github-attachments.ts#L482-L518","documentation":"While streaming the canonical GitHub API response body, githubAttachmentCommentFetch accumulates bytes and aborts with GitHubAttachmentUnavailableError('github_attachment_canonical_api_too_large') as soon as the cumulative size exceeds MAX_COMMENT_RESPONSE_BYTES. This guards against a lying or absent Content-Length and caps memory use before the body is parsed.","triggerScenarios":"The streamed body of the canonical comment API response exceeds MAX_COMMENT_RESPONSE_BYTES mid-read, either because Content-Length was absent/incorrect (chunked transfer) or because the comment payload genuinely grew beyond the cap between the header check and the stream.","commonSituations":"A very large comment body or HTML rendering pushed a comment past the byte cap; a compromised/misbehaving upstream sends chunked data without Content-Length, bypassing the header check but hitting the stream counter.","solutions":["Reduce the comment size on GitHub (shorten body, remove large HTML blocks) and re-attach the file.","Check the server's MAX_COMMENT_RESPONSE_BYTES configuration if large attachments are legitimate for your deployment.","If the Content-Length header check passed but the stream still overflowed, verify the proxy/upstream is not inflating the response (e.g. injecting content or re-encoding)."],"exampleFix":"// before: trusting Content-Length alone\nif (Number(res.headers.get('content-length') ?? 0) > MAX) throw new TooLarge();\n// after: also enforce the cap while streaming\nlet size = 0;\nfor await (const chunk of res.body) { size += chunk.byteLength; if (size > MAX) throw new TooLarge(); }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const value = await fetchCanonicalComment(url, signal);\n} catch (e) {\n  if (e instanceof GitHubAttachmentUnavailableError && e.code === 'github_attachment_canonical_api_too_large') {\n    log.warn('comment response exceeded byte cap', { url });\n    return { kind: 'unavailable', reason: 'too_large' };\n  }\n  throw e;\n}","preventionTips":["Keep comment bodies modest; strip or avoid pasting very large HTML/blocks into the source comment.","Remember the cap applies twice: on the declared Content-Length and again on the streamed byte count — a missing/chunked Content-Length only fails at the stream stage.","If large responses are legitimate, raise MAX_COMMENT_RESPONSE_BYTES deliberately, not silently.","Monitor for upstreams/proxies that strip Content-Length (chunked encoding) to know the header pre-check will not protect you."],"tags":["github","http","payload-size","streaming"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}