denoland/deno · error · Error

ERR_HTTP2_GOAWAY_SESSION

ERR_HTTP2_GOAWAY_SESSION

Error message

New streams cannot be created after receiving a GOAWAY

What it means

Thrown by ClientHttp2Session.request() when the session is closed — this.closed is true — which happens after a GOAWAY frame is received (server graceful shutdown) or close() has been initiated. Per HTTP/2, a GOAWAY tells the client the server will not accept new streams, so request() refuses with ERR_HTTP2_GOAWAY_SESSION even though existing streams may still drain. The destroyed check runs first, so a not-yet-destroyed but closed session produces exactly this error.

Source

Thrown at ext/node/polyfills/http2.ts:4692

// has been connected.
class ClientHttp2Session extends Http2Session {
  constructor(options, socket) {
    initCallbacks();
    super(NGHTTP2_SESSION_CLIENT, options, socket);
    this[kPendingRequestCalls] = null;
  }

  // Submits a new HTTP2 request to the connected peer. Returns the
  // associated Http2Stream instance.
  request(headersParam, options) {
    debugSessionObj(this, "initiating request");

    if (this.destroyed) {
      throw new ERR_HTTP2_INVALID_SESSION();
    }

    if (this.closed) {
      throw new ERR_HTTP2_GOAWAY_SESSION();
    }

    this[kUpdateTimer]();

    let span;
    if (otelState.TRACING_ENABLED) {
      // Determine the method name for the span; mirrors the default applied
      // by prepareRequestHeaders{Object,Array} below.
      let spanMethod;
      if (ArrayIsArray(headersParam)) {
        for (let i = 0; i < headersParam.length; i += 2) {
          if (
            StringPrototypeToLowerCase(headersParam[i]) === HTTP2_HEADER_METHOD
          ) {
            spanMethod = headersParam[i + 1];
            break;
          }
        }

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Catch ERR_HTTP2_GOAWAY_SESSION (and ERR_HTTP2_INVALID_SESSION), create a new session via http2.connect(), and retry the request — the request was never sent, so a retry is safe
  2. Listen for the session 'goaway' event and proactively evict the session from your pool
  3. For servers that GOAWAY after N requests, rotate sessions yourself before hitting the limit

Example fix

// before
const stream = session.request(headers); // throws after server sent GOAWAY

// after
async function requestRetry(headers) {
  try {
    return session.request(headers);
  } catch (e) {
    if (e.code !== 'ERR_HTTP2_GOAWAY_SESSION' && e.code !== 'ERR_HTTP2_INVALID_SESSION') throw e;
    session = http2.connect(origin);
    return session.request(headers);
  }
}
Defensive patterns

Strategy: retry

Validate before calling

if (session.closed || session.destroyed) {
  session = http2.connect(origin);
}
const stream = session.request(headers);

Type guard

function canOpenNewStream(s) {
  return !s.destroyed && !s.closed;
}

Try / catch

try {
  stream = session.request(headers);
} catch (e) {
  if (e.code !== 'ERR_HTTP2_GOAWAY_SESSION' && e.code !== 'ERR_HTTP2_INVALID_SESSION') throw e;
  session = http2.connect(origin); // GOAWAY means the request never left
  stream = session.request(headers);
}

Prevention

When it happens

Trigger: Issuing request() after the peer sent GOAWAY (graceful shutdown, server restart, LB draining); racing the 'goaway' event — you picked the session before GOAWAY arrived but called request() after; calling request() after your own session.close().

Common situations: Long-lived clients against servers that rotate connections periodically (e.g. after N requests or T seconds); load-balancer draining windows; connection pools with no GOAWAY awareness, so requests keep failing until the pool refreshes.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/44ceaebbf621d7ff. Report an issue: GitHub.