oven-sh/bun · error · TypeError

WriteFailed

WriteFailed

Error message

WriteFailed

What it means

Writing to the socket failed inside the HTTP client — the h2 session sets it when flushing the write buffer or sending frames hits an I/O error (src/http/h2_client/ClientSession.rs:782, :820, :917; src/http/lib.rs:1480). In practice the peer closed the connection while Bun was still writing (EPIPE/ECONNRESET), so the request fails mid-body. The same variant name is reused by other crates' enums for file-write failures, but in bun_http::Error it is specifically a socket/stream write failure.

Source

Thrown at src/http/error.rs:8

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum Error {
    #[error("CompressionFailed")]
    CompressionFailed,
    #[error("Aborted")]
    Aborted,
    #[error("WriteFailed")]
    WriteFailed,
    #[error("HTTP2RefusedStream")]
    HTTP2RefusedStream,
    #[error("HTTP2ContentLengthMismatch")]
    HTTP2ContentLengthMismatch,
    #[error("HTTP2FrameSizeError")]
    HTTP2FrameSizeError,
    #[error("HTTP2ProtocolError")]
    HTTP2ProtocolError,
    #[error("HTTP2FlowControlError")]
    HTTP2FlowControlError,
    #[error("HTTP2EnhanceYourCalm")]
    HTTP2EnhanceYourCalm,
    #[error("HTTP2HeaderListTooLarge")]
    HTTP2HeaderListTooLarge,
    #[error("HTTP2StreamReset")]
    HTTP2StreamReset,
    #[error("HTTP2GoAway")]

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Retry the request — the connection is dead, but a fresh one usually succeeds; keep the body buffered so it is reusable.
  2. Send the request sooner after acquiring a pooled connection, or enable smaller keep-alive windows on your side so stale sockets are pruned.
  3. For long uploads, raise the server/proxy timeout (proxy_read_timeout, LB idle timeout) above your worst-case upload duration.
  4. If it happens consistently against one endpoint, capture `tcpdump`/server logs to see which side closes first — a server-side 408/499 preceding the close means the app is too slow, not the network.

Example fix

// before
const res = await fetch(url, { method: 'POST', body }); // fails once with WriteFailed after idle reuse
// after: retry once on write failure (idempotent request)
async function post(url, body) {
  for (let i = 0; ; i++) {
    try { return await fetch(url, { method: 'POST', body }); }
    catch (e) { if (i >= 1 || !/WriteFailed|EPIPE|ECONNRESET/i.test(String(e.message))) throw e; }
  }
}
Defensive patterns

Strategy: retry

Type guard

function isWriteFailure(err) {
  const s = String(err?.message ?? err);
  return /WriteFailed|EPIPE|ECONNRESET|broken pipe/i.test(s);
}

Try / catch

async function fetchRetryWrite(url, init, tries = 2) {
  for (let i = 0; ; i++) {
    try { return await fetch(url, init); }
    catch (err) {
      if (i >= tries || !isWriteFailure(err)) throw err;
      await Bun.sleep(50 * (i + 1)); // new connection on retry
    }
  }
}

Prevention

When it happens

Trigger: POSTing a request body to a server that already closed the connection (keep-alive race, server-side timeout, LB idle cutoff), writing to an h2 session after GOAWAY processing began, or a peer resetting the TCP connection mid-write.

Common situations: Keep-alive connections reused after the server's idle timeout (common with nginx keepalive_timeout shorter than the client pool's); proxies closing slow uploads; servers with aggressive request timeouts (e.g. 30s body limit) killing long uploads.

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/f3942be82ac4ea18. Report an issue: GitHub.