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
- 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
- Listen for the session 'goaway' event and proactively evict the session from your pool
- 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
- Listen for the 'goaway' event and drain/evict pooled sessions immediately
- The request that hits this error was never sent — retrying it on a new session is safe
- Existing streams survive GOAWAY; only NEW streams are refused
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
- ERR_HTTP2_UNSUPPORTED_PROTOCOL
- ERR_INVALID_ARG_TYPE
- ERR_INVALID_URL
- ERR_HTTP2_NO_SOCKET_MANIPULATION
- ERR_HTTP2_SOCKET_UNBOUND
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/44ceaebbf621d7ff.
Report an issue: GitHub.