{"record":{"id":"eb43e2ea797f1a27","repo":"can1357/oh-my-pi","slug":"no-response-body-eb43e2","errorCode":null,"errorMessage":"No response body","messagePattern":"No response body","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/utils/tools-manager.ts","lineNumber":206,"sourceCode":"\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n/** Download a tool asset without handing the streaming Response to Bun.write. */\nexport async function downloadFile(url: string, dest: string, signal?: AbortSignal): Promise<void> {\n\tconst downloadSignal = ptree.combineSignals(signal, TOOL_DOWNLOAD_TIMEOUT_MS);\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tsignal: downloadSignal,\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t\t} else if (!response.body) {\n\t\t\tthrow new Error(\"No response body\");\n\t\t}\n\t\tawait writeResponseBody(dest, response.body, downloadSignal);\n\t} catch (err) {\n\t\tif (isAbortLikeError(err)) {\n\t\t\tthrow new Error(`Download timed out: ${url}`);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n// Download and install a tool\nasync function downloadTool(tool: ToolName, signal?: AbortSignal): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = os.platform();\n\tconst architecture = os.arch();\n","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/utils/tools-manager.ts#L188-L224","documentation":"After a successful (ok) fetch, downloadFile() expects a streaming response body to write to the destination file. If response.body is null — which the fetch spec permits in rare cases even for OK responses — it throws this error rather than silently producing an empty file.","triggerScenarios":"The asset fetch returned response.ok === true but response.body === null, so writeResponseBody cannot be called and downloadFile throws \"No response body\".","commonSituations":"Unusual proxy or middleware stripping/terminating the body, non-standard HTTP responses intercepted by network appliances, or a corrupted/edge-case response from GitHub's CDN.","solutions":["Retry the download — this is almost always a transient or network-middlebox artifact, not a code bug.","Disable or bypass proxies/VPNs/security appliances that may rewrite the HTTP response.","Verify with curl that the asset URL returns a real body (curl -I to check Content-Length).","If reproducible behind a specific proxy, download the asset manually and place it in the tools directory."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// pre-flight: confirm the URL serves a body | const head = await fetch(assetUrl, { method: \"HEAD\" }); if (!head.ok || head.headers.get(\"content-length\") === \"0\") throw new Error(\"Asset has no body\");","typeGuard":"function isEmptyBodyError(err: unknown): err is Error { return err instanceof Error && err.message === \"No response body\"; }","tryCatchPattern":"try { const p = await toolsManager.download(\"sg\"); } catch (err) { if (isEmptyBodyError(err)) { await Bun.sleep(2000); /* transient middlebox artifact; retry */ } else throw err; }","preventionTips":["Retry — this error is almost always transient.","Bypass proxies/security appliances when downloading release assets.","Verify asset URLs with curl -I when debugging.","Report persistent occurrences with proxy/network details."],"tags":["network","download","fetch"],"backgroundTag":"empty-response-body","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}