Symptom
Every OpenAI call from Cipher fails on Node 26.3.1:
Error in OpenAI API call (Attempt 1/3): Invalid response body while trying to fetch https://api.openai.com/v1/chat/completions: Premature close
Error in OpenAI API call (Attempt 2/3): Invalid response body while trying to fetch https://api.openai.com/v1/chat/completions: Premature close
Error in OpenAI API call (Attempt 3/3): Invalid response body while trying to fetch https://api.openai.com/v1/chat/completions: Premature close
Failed to get response from OpenAI after 3 attempts.
Result: agent breadcrumbs (checkpoint_session), meeting recaps, and ask_cipher all silently fail. Worked perfectly under Node 26.3.0.
Environment
|
|
| Cipher |
0.3.0 (latest at time of writing) |
| Node |
26.3.1 (current) — broken |
| Node |
26.3.0 — worked fine, same Cipher |
| openai-sdk |
4.104.0 (latest 4.x, pinned by Cipher) |
| OS |
macOS 15.6 (Darwin 24.6.0) |
Root cause (verified)
- Node 26.3.1 bumped internal undici 8.4 → 8.5. This broke
node-fetch@2.7.0's stream handling. The wrapped error Invalid response body while trying to fetch URL: Premature close is node-fetch's own format (see node-fetch/lib/index.js:217 and :1531), thrown when the response stream is closed before fetch is done reading it.
- openai-sdk@4.x uses node-fetch directly via its node-runtime shim (
openai/_shims/node-runtime.js:74: fetch: nf.default). So overriding globalThis.fetch doesn't help — openai-sdk never reads it.
- Same shim worked fine on Node 26.3.0 with same node-fetch 2.7.
Isolated the issue with a 10-line undici@6 roundtrip script — undici.fetch to api.openai.com/v1/chat/completions returns a clean 401 with full JSON body on Node 26.3.1. So undici@6 works, node-fetch@2.7 doesn't, on the same runtime.
Verified fix
Swap node-fetch for undici inside openai-sdk's shim. Two compat issues need sanitization:
- openai-sdk passes Node's
http.Agent via init.agent (from getDefaultAgent) — undici doesn't accept it.
- openai-sdk passes a pre-computed
Content-Length header — undici rejects with invalid content-length header.
Strip both before forwarding to undici. Patch for node_modules/openai/_shims/node-runtime.js around line 74:
// CC-309-OPENAI-SHIM-PATCH
var __realFetch;
try { __realFetch = require('undici').fetch; }
catch (e) { __realFetch = nf.default; }
var __sanitizedFetch = function(url, init) {
if (init) {
var clean = {};
for (var k in init) { if (k !== 'agent') clean[k] = init[k]; }
if (clean.headers) {
var cleanHeaders = {};
var src = clean.headers;
var keys = (typeof src.keys === 'function') ? Array.from(src.keys()) : Object.keys(src);
for (var i = 0; i < keys.length; i++) {
var hk = keys[i];
if (hk.toLowerCase() === 'content-length') continue;
cleanHeaders[hk] = (typeof src.get === 'function') ? src.get(hk) : src[hk];
}
clean.headers = cleanHeaders;
}
init = clean;
}
return __realFetch(url, init);
};
return {
kind: 'node',
fetch: __sanitizedFetch,
// ... rest of returned object unchanged
undici already ships in Cipher's dependency tree.
Verified locally
$ cipher --no-verbose --agent ~/.mcp/cipher.yml "Reply with the single word: pong"
┌ 🤖 AI Response ┐
│ │
│ pong │
│ │
└────────────────┘
Why filing here (not openai-sdk)
This is technically openai-sdk + Node-26 + node-fetch tooth-grind, but:
- openai-sdk 5.x is out and may have moved off node-fetch. 4.x is in maintenance mode — a fix there is uncertain.
- Cipher controls which
openai version it pins. Either bump openai to a Node-26-compatible release, or vendor-patch this shim, or fork.
Would be great to see one of those land in a Cipher release so users on Node 26.x don't have to do this manually.
Happy to PR
If a vendor-patched shim or a postinstall script fits your release process, I can send a PR. Let me know which shape you'd prefer.
Symptom
Every OpenAI call from Cipher fails on Node 26.3.1:
Result: agent breadcrumbs (
checkpoint_session), meeting recaps, andask_cipherall silently fail. Worked perfectly under Node 26.3.0.Environment
Root cause (verified)
node-fetch@2.7.0's stream handling. The wrapped errorInvalid response body while trying to fetch URL: Premature closeis node-fetch's own format (seenode-fetch/lib/index.js:217and:1531), thrown when the response stream is closed before fetch is done reading it.openai/_shims/node-runtime.js:74:fetch: nf.default). So overridingglobalThis.fetchdoesn't help — openai-sdk never reads it.Isolated the issue with a 10-line
undici@6roundtrip script —undici.fetchtoapi.openai.com/v1/chat/completionsreturns a clean 401 with full JSON body on Node 26.3.1. Soundici@6works,node-fetch@2.7doesn't, on the same runtime.Verified fix
Swap
node-fetchforundiciinside openai-sdk's shim. Two compat issues need sanitization:http.Agentviainit.agent(fromgetDefaultAgent) — undici doesn't accept it.Content-Lengthheader — undici rejects withinvalid content-length header.Strip both before forwarding to undici. Patch for
node_modules/openai/_shims/node-runtime.jsaround line 74:undicialready ships in Cipher's dependency tree.Verified locally
Why filing here (not openai-sdk)
This is technically openai-sdk + Node-26 + node-fetch tooth-grind, but:
openaiversion it pins. Either bump openai to a Node-26-compatible release, or vendor-patch this shim, or fork.Would be great to see one of those land in a Cipher release so users on Node 26.x don't have to do this manually.
Happy to PR
If a vendor-patched shim or a postinstall script fits your release process, I can send a PR. Let me know which shape you'd prefer.