{"record":{"id":"3b2355c28089b3c0","repo":"oven-sh/bun","slug":"http2refusedstream","errorCode":"HTTP2RefusedStream","errorMessage":"HTTP2RefusedStream","messagePattern":"HTTP2RefusedStream","errorType":"error_code","errorClass":"TypeError","httpStatus":null,"severity":"warning","filePath":"src/http/error.rs","lineNumber":10,"sourceCode":"#[allow(non_camel_case_types)]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]\npub enum Error {\n    #[error(\"CompressionFailed\")]\n    CompressionFailed,\n    #[error(\"Aborted\")]\n    Aborted,\n    #[error(\"WriteFailed\")]\n    WriteFailed,\n    #[error(\"HTTP2RefusedStream\")]\n    HTTP2RefusedStream,\n    #[error(\"HTTP2ContentLengthMismatch\")]\n    HTTP2ContentLengthMismatch,\n    #[error(\"HTTP2FrameSizeError\")]\n    HTTP2FrameSizeError,\n    #[error(\"HTTP2ProtocolError\")]\n    HTTP2ProtocolError,\n    #[error(\"HTTP2FlowControlError\")]\n    HTTP2FlowControlError,\n    #[error(\"HTTP2EnhanceYourCalm\")]\n    HTTP2EnhanceYourCalm,\n    #[error(\"HTTP2HeaderListTooLarge\")]\n    HTTP2HeaderListTooLarge,\n    #[error(\"HTTP2StreamReset\")]\n    HTTP2StreamReset,\n    #[error(\"HTTP2GoAway\")]\n    HTTP2GoAway,\n    #[error(\"HTTP2CompressionError\")]","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/oven-sh/bun/blob/8c5296ac459e8252d3cd702f3fbcbb0c249d95d5/src/http/error.rs#L1-L28","documentation":"The HTTP/2 server refused the stream: either an RST_STREAM with error code REFUSED_STREAM (src/http/h2_client/dispatch.rs:505-507) or a graceful GOAWAY whose last-stream-id excludes this stream (src/http/h2_client/dispatch.rs:529-534). Per RFC 9113 this signals 'retry on a new connection'. Bun already retries transparently when safe — no response bytes seen, buffered body, retries < MAX_H2_RETRIES (src/http/h2_client/ClientSession.rs:1099-1111) — so seeing the error means retries were exhausted, the body was a stream, or headers had already arrived.","triggerScenarios":"Hitting a server mid-graceful-shutdown or at its max-concurrent-streams limit repeatedly; long-lived h2 connections during server rolling deploys; streamed request bodies (not retryable by Bun) refused at stream-open.","commonSituations":"Deploy churn behind ALBs/Envoy that drain h2 connections; servers configured with very low SETTINGS_MAX_CONCURRENT_STREAMS; bursty clients outrunning stream capacity on shared connections.","solutions":["Retry the request after a short backoff — the condition is by definition transient (a new connection gets a new stream).","For streaming bodies, buffer the body if the request is idempotent, so Bun's built-in h2 retry can engage.","Reduce burst concurrency or spread requests across connections when the server's max-concurrent-streams is the limiter.","If it persists against one host, check its drain/timeout settings (e.g. Envoy http2_max_requests, graceful shutdown windows)."],"exampleFix":"// before: single attempt on a streaming body\nawait fetch(url, { method: 'POST', body: stream, keepalive: true });\n// after: buffered body + caller-side retry for refused streams\nconst buf = await new Response(stream).arrayBuffer();\nfor (let i = 0; ; i++) {\n  try { return await fetch(url, { method: 'POST', body: buf }); }\n  catch (e) {\n    if (i >= 2 || !String(e.message).includes('HTTP2RefusedStream')) throw e;\n    await Bun.sleep(50 * 2 ** i);\n  }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isRefusedStream(err) {\n  return /HTTP2RefusedStream/i.test(String(err?.message ?? err));\n}","tryCatchPattern":"async function fetchRetryRefused(url, init, tries = 3) {\n  for (let i = 0; ;i++) {\n    try { return await fetch(url, init); }\n    catch (err) {\n      if (i >= tries - 1 || !isRefusedStream(err)) throw err;\n      await Bun.sleep(50 * 2 ** i); // server draining — new connection next try\n    }\n  }\n}","preventionTips":["Buffer request bodies when possible so Bun's built-in h2 refused-stream retry can engage","Add jittered retry with backoff around fetches during known deploy windows","Watch for systemic causes: server max-concurrent-streams too low, LB draining too aggressively"],"tags":["http","http2","network","retry","fetch"],"backgroundTag":null,"analyzedSha":"8c5296ac459e8252d3cd702f3fbcbb0c249d95d5","analyzedAt":"2026-08-16T08:01:58.794Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}