{"record":{"id":"17632056f7a03775","repo":"paperclipai/paperclip","slug":"github-attachment-download-failed","errorCode":"github_attachment_download_failed","errorMessage":"github_attachment_download_failed","messagePattern":"github_attachment_download_failed","errorType":"error_code","errorClass":"GitHubAttachmentUnavailableError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-github-attachments.ts","lineNumber":852,"sourceCode":"        type: mimeType.startsWith(\"image/\")\n          ? \"image\"\n          : mimeType.startsWith(\"audio/\")\n            ? \"audio\"\n            : mimeType.startsWith(\"video/\")\n              ? \"video\"\n              : \"file\",\n        name,\n        mimeType,\n        size,\n        fetchData: async () => body,\n      };\n    }\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_unsafe_redirect\",\n    );\n  } catch (error) {\n    if (error instanceof GitHubAttachmentUnavailableError) throw error;\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_download_failed\",\n    );\n  }\n}\n","sourceCodeStart":834,"sourceCodeEnd":857,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-github-attachments.ts#L834-L857","documentation":"The outer catch of prepareGitHubPublicAttachment (chat-github-attachments.ts:850-855) rethrows GitHubAttachmentUnavailableError as-is, but wraps any other thrown value — network failures, DNS errors, timeouts, SSRF-guard rejections from guardedRemoteHttpFetch, AbortSignal timeouts, URL parse errors — into a generic GitHubAttachmentUnavailableError with code \"github_attachment_download_failed\". It is the umbrella error meaning the download could not be completed for a reason not covered by a more specific code.","triggerScenarios":"guardedRemoteHttpFetch rejects (connect timeout 5000ms, private-network target blocked, DNS failure); AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) fires; new URL(locator.url) throws on a malformed stored URL; any non-GitHubAttachmentUnavailableError exception inside the try block.","commonSituations":"No outbound internet access / DNS misconfiguration on the server host; corporate firewall blocking objects.githubusercontent.com; long downloads exceeding DOWNLOAD_TIMEOUT_MS; attachment handles whose stored locator URL got corrupted; IPv6 vs IPv4 egress issues.","solutions":["Check server egress: curl the attachment URL from the server host to test DNS/firewall reachability","Verify DOWNLOAD_TIMEOUT_MS is adequate for large attachments on slow links","Log/inspect the underlying cause — the original error is swallowed, so reproduce with the same URL outside the wrapper","Confirm the attachment locator URL is a valid absolute http(s) URL"],"exampleFix":"// before: opaque failure with no visibility\n} catch (error) {\n  throw new GitHubAttachmentUnavailableError(\"github_attachment_download_failed\");\n}\n// after: preserve the cause for diagnosis\n} catch (error) {\n  if (error instanceof GitHubAttachmentUnavailableError) throw error;\n  logger.warn(\"github attachment download failed\", { cause: String(error) });\n  throw new GitHubAttachmentUnavailableError(\"github_attachment_download_failed\");\n}","handlingStrategy":"retry","validationCode":"const u = new URL(attachment.url); if (u.protocol !== \"https:\") throw new Error(\"attachment locator must be https\"); // check egress once at boot: await fetch(\"https://github.com/robots.txt\")","typeGuard":"function hasValidLocator(a) { if (!a?.url) return false; try { const u = new URL(a.url); return u.protocol === \"https:\"; } catch { return false; } }","tryCatchPattern":"try { return await prepareGitHubPublicAttachment(attachment, signal, resolveComment); } catch (e) { if (e?.code === \"github_attachment_download_failed\") { if (isRetryable(signal)) return retry(prepareGitHubPublicAttachment, attachment); return downloadFailedCard(attachment); } throw e; }","preventionTips":["Allowlist egress to github.com and objects.githubusercontent.com in firewall/proxy config","Size DOWNLOAD_TIMEOUT_MS for the largest expected attachment","Preserve/log the underlying cause when wrapping errors","Validate stored attachment URLs are absolute https URLs"],"tags":["github","attachments","network","timeout"],"backgroundTag":"http-request-failed","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"}