Repository navigation
Description
What version of Bun is running?
1.4.0-canary.1+346ce0805
What platform is your computer?
Darwin 25.5.0 arm64
What steps can reproduce the bug?
This reproduces with @cursor/[email protected] when running a local Cursor SDK agent against a git workspace. The same script succeeds in Node.js.
mkdir /tmp/cursor-sdk-bun-http2-repro
cd /tmp/cursor-sdk-bun-http2-repro
bun init -y
bun add @cursor/sdkCreate repro.mjs:
import { execFileSync } from "node:child_process";
import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent } from "@cursor/sdk";
const apiKey = process.env.CURSOR_API_KEY;
if (!apiKey) {
throw new Error("Set CURSOR_API_KEY before running this repro");
}
const cwd = await mkdtemp(join(tmpdir(), "cursor-sdk-git-repro-"));
await writeFile(
join(cwd, "README.md"),
"# Cursor SDK Bun HTTP/2 repro\n\nTiny local git repo for reproducing the stream failure.\n",
"utf8",
);
execFileSync("git", ["init"], { cwd, stdio: "inherit" });
execFileSync("git", ["add", "README.md"], { cwd, stdio: "inherit" });
execFileSync(
"git",
["-c", "user.name=Repro", "-c", "[email protected]", "commit", "-m", "init"],
{ cwd, stdio: "inherit" },
);
const agent = await Agent.create({
apiKey,
model: { id: "composer-2" },
local: { cwd },
});
const run = await agent.send(
"Read README.md and answer in one short sentence. Do not modify files.",
);
console.log(JSON.stringify({ event: "started", agentId: agent.agentId, runId: run.id }));
for await (const message of run.stream()) {
console.log(
JSON.stringify({
event: "stream",
type: message.type,
status: message.status,
name: message.name,
text: message.message?.content?.[0]?.text,
}),
);
}
const result = await run.wait();
console.log(JSON.stringify({ event: "result", status: result.status, result: result.result }));Run with Bun:
CURSOR_API_KEY=... bun repro.mjsFor comparison, run the same script with Node.js:
CURSOR_API_KEY=... node repro.mjsWhat is the expected behavior?
Bun should be able to run the Cursor SDK local agent stream the same way Node.js does. The stream should deliver tool/assistant/status events and finish successfully.
What do you see instead?
With Bun, the run starts and usually emits an initial RUNNING status event, then the node:http2 stream fails with:
ConnectError: [internal] Stream closed with error code NGHTTP2_FRAME_SIZE_ERROR
rawMessage: "Stream closed with error code NGHTTP2_FRAME_SIZE_ERROR",
code: 13,
metadata: Headers {},
details: [],
at from (.../node_modules/@connectrpc/connect/dist/esm/connect-error.js:71:20)
at connectErrorFromNodeReason (.../node_modules/@connectrpc/connect-node/dist/esm/node-error.js:52:29)
at reject (.../node_modules/@connectrpc/connect-node/dist/esm/node-universal-client.js:293:63)
at h2StreamError (.../node_modules/@connectrpc/connect-node/dist/esm/node-universal-client.js:182:22)
error: Stream closed with error code NGHTTP2_FRAME_SIZE_ERROR
code: "ERR_HTTP2_STREAM_ERROR"
Bun v1.4.0-canary.1+346ce0805 (macOS arm64)
With Node.js v24.16.0, the exact same script and workspace stream successfully and return a finished result.
Additional information
This may be related to #25589, but that issue is closed and was about a generic gRPC request/frame-size regression. This repro is specifically for @cursor/sdk local agents, which use @connectrpc/connect-node / node:http2 streaming internally.
One observed detail: if the local workspace is a plain temporary directory, Bun may complete successfully. Initializing the workspace as a git repository makes the local Cursor SDK agent path consistently hit the HTTP/2 stream failure in this repro.
Activity
github-actions commentedon May 28, 2026
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
xiehaibin18 commentedon May 28, 2026
Additional data point: I also reproduced this with the current stable Bun release, not only canary.
1.3.14+0d9b296afv24.16.0@cursor/[email protected]Darwin 25.5.0 arm64Using the same temporary git workspace and the same SDK script:
[email protected]starts the run and emits initial stream events, but repeatedly logsConnectError: [internal] Stream closed with error code NGHTTP2_FRAME_SIZE_ERRORfrom@connectrpc/connect-node/node:http2. The run eventually returnsFINISHED, but the agent reports that file/shell access failed and it cannot readREADME.md.[email protected]on the exact same workspace streams normally: thereadtool call completes, assistant text is streamed, and the run returns a successfulfinishedresult.So the repro is not limited to
1.4.0-canary.1; stable1.3.14also shows the HTTP/2 stream/tool-call failure for this Cursor SDK local git workspace path.robobun commentedon May 28, 2026
Unable to reproduce this.
Could not reproduce the
NGHTTP2_FRAME_SIZE_ERRORin a self-contained HTTP/2 testReported on:
1.4.0-canary.1+346ce0805(macOS arm64)Tested with: debug build of
main@3ec6669844, and Node.jsv24.3.0, onlinux x64The reported failure only happens inside the real
@cursor/sdklocal-agent flow, which needs a paidCURSOR_API_KEYand a live connection to Cursor's relay server. I don't have that credential, so I couldn't run the exact script. Instead I tried to reproduce the underlyingnode:http2client behavior that@connectrpc/connect-nodeexercises. In every self-contained reproduction, Bun behaved identically to Node and did not produceNGHTTP2_FRAME_SIZE_ERROR.What the error actually is
@connectrpc/connect-nodesurfaces this inh2StreamError(itsstream.on("error", ...)handler). It falls through to the defaultCode.Internal(=code: 13, matching the[internal]prefix in the report). Bun raisesERR_HTTP2_STREAM_ERRORwith messageStream closed with error code NGHTTP2_FRAME_SIZE_ERRORwhen a stream is closed with RST code0x6(FRAME_SIZE_ERROR). For an inbound DATA frame that is itself too large, Bun sends a connection-level GOAWAY, not a stream RST — so the most likely origin of the report is an RST_STREAM(FRAME_SIZE_ERROR) received from the Cursor relay server, i.e. the server rejecting a specific frame Bun sent.Attempt 1 — large streamed POST body (h2c), Bun client → Node server
Server on default settings (
maxFrameSize: 16384); client writes 50 KB + 20 KB + 5 KB thenend().Node baseline is identical. Bun correctly splits the body into ≤16 KB DATA frames.
Attempt 2 — body-size sweep across the 16384-byte frame boundary
Single
req.end(Buffer.alloc(size))for each size, Bun client → Node server:No frame-size error at any boundary.
Attempt 3 — many queued writes +
PADDING_STRATEGY_MAX2000 rapid
req.write()calls (≈1.29 MB) without waiting for drain, forcing the internal queue/flush_queuesplitting path, with max padding enabled:Node baseline identical. The queued/backpressure DATA path and padding stay within the frame-size limit.
Attempt 4 — TLS (h2) + server streaming + gRPC-style trailers
This mirrors connectrpc: TLS
createSecureServer, server writes ~1.2 MB in 4 KB chunks thensendTrailers({ "grpc-status": "0" }); Bun client reads response + trailers, sends an 8 KB request body.Node baseline identical. TLS h2, server-streaming, and trailing HEADERS all work.
Conclusion
Bun's generic
node:http2client framing — DATA splitting, HEADERS/CONTINUATION (the code consults the peer'sSETTINGS_MAX_FRAME_SIZE), trailers, TLS, backpressure queuing, and padding — is correct in every self-contained test I could build, and matches Node.js. TheNGHTTP2_FRAME_SIZE_ERRORis a stream reset whose trigger lives in the live@cursor/sdk↔ Cursor-relay exchange, which I can't reproduce here without aCURSOR_API_KEYand knowledge of the exact frame sequence the server rejects.To move this forward, the most useful thing would be an HTTP/2 frame trace so we can see which frame Bun sends that the server rejects. Please capture one of:
NODE_DEBUG_NATIVE=http2(orNODE_DEBUG=http2) output from the failing Bun run, and from the passing Node run, so the two frame streams can be compared; ortcpdump/Wireshark capture of the h2 session (decrypt withSSLKEYLOGFILE), orBUN_DEBUG_H2FrameParser=1 bun repro.mjs 2>h2.log(Bun's internal HTTP/2 frame log) from a failing run.The specific frame sizes involved (especially around the larger request content a git workspace produces) would let us pinpoint the divergence. A smaller self-contained repro that doesn't need the Cursor API — even one that reproduces intermittently — would also unblock this.
If you can provide a minimal reproduction (exact command, Bun version, platform), please update the issue.
Automated investigation. Reopen with more context if this is wrong.
xiehaibin18 commentedon May 28, 2026
I ran a smaller differential trace against the same tiny local git repo with
@cursor/sdklocal agent. Summary: Bun still fails withNGHTTP2_FRAME_SIZE_ERROR; Node succeeds on the same fixture.Environment:
@cursor/sdk^1.0.14[email protected]vianpm exec --yes [email protected] -- ...v24.16.0Repro shape:
Commands used:
Bun result, from stdout:
Bun stderr repeatedly shows the same Connect error:
Node result on the same temp git repo:
Node
NODE_DEBUG_NATIVE=http2 NODE_DEBUG=http2sample, showing normal HTTP/2 progress and stream close with code 0:One note: in this npm-installed
[email protected]run,BUN_DEBUG_H2FrameParser=1did not appear to emit the low-level frame parser rows you asked for. The Bun failure is still visible at the Cursor SDK / ConnectError layer, while Node's native HTTP/2 debug trace shows the same local-agent task completing successfully.