sindresorhus/got · error · Error
The session is gracefully closing. No new streams are allowe
Error message
The session is gracefully closing. No new streams are allowed.
What it means
From the pooled HTTP/2 session manager at http2-client.ts:1008: when a session is mid-graceful-close (`session.gracefullyClosing = true`, set during idle reaping at line 965), any new `session.request()` call throws because opening a stream on a closing session would race the GOAWAY. The throw is the pool's way of saying 'pick a different session'; normally the pool retries on another session, but if every session is closing this surfaces to the caller.
Source
Thrown at source/core/utils/http2-client.ts:1008
clearSessionSetup();
entry.reject(new Error('HTTP/2 session setup canceled'));
session.destroy();
};
const request = session.request.bind(session);
session.request = (headers, streamOptions) => {
const hasReservedStream = (session.reservedStreamCount ?? 0) > 0;
if (hasReservedStream) {
session.reservedStreamCount = session.reservedStreamCount! - 1;
}
if (session.gracefullyClosing) {
if (hasReservedStream) {
this.releaseStream(session, shouldPoolSession);
}
throw new Error('The session is gracefully closing. No new streams are allowed.');
}
if (!hasReservedStream) {
this.reserveStream(session);
}
let stream: ClientHttp2Stream;
try {
stream = request(headers, streamOptions);
} catch (error: unknown) {
this.releaseStream(session, shouldPoolSession);
throw error;
}
stream.once('close', () => {
this.releaseStream(session, shouldPoolSession);
});
View on GitHub (pinned to e3924aa1e5)
Solutions
- Let Got manage the pool (don't reuse a captured `h2session` for new requests).
- Increase `http2.maxConcurrentStreams` and the pool's session count so reaping is less aggressive.
- Catch and retry the request once — Got usually picks a fresh session on the next attempt.
- If using `h2session` directly, open all streams before the session goes idle, or request a new session per logical batch.
Example fix
// before: reusing a captured session
const session = await got(..., {http2: true, h2session: ref});
// ref started closing -> next call throws
// after: let the pool manage sessions
await got(url, {http2: true}); Defensive patterns
Strategy: retry
Validate before calling
// Nothing to validate pre-call; the error is a transient pool state.
// Use a bounded retry instead:
async function resilientGot(url, options, attempts = 2) {
for (let i = 0; i < attempts; i++) {
try {
return await got(url, options);
} catch (error) {
if (i === attempts - 1 || !/gracefully closing/i.test(error.message)) throw error;
}
}
} Try / catch
try {
await got(url, {http2: true});
} catch (error) {
if (/gracefully closing/i.test(error.message)) {
// session was mid-close; retry once and Got will pick a fresh session
await got(url, {http2: true});
} else throw error;
} Prevention
- Let Got own the HTTP/2 session pool — don't capture and reuse `h2session`.
- Tune `http2.maxConcurrentStreams` and pool size for bursty traffic.
- Wrap HTTP/2 calls in a single bounded retry for transient pool races.
- Avoid holding idle sessions long enough to be reaped mid-burst.
When it happens
Trigger: High-concurrency HTTP/2 traffic where the pool reaps an idle session between the moment Got selects it and the moment the stream opens; or calling `session.request(...)` directly on a session you obtained via `h2session`. Also fires under bursty traffic that exhausts maxConcurrentStreams on remaining live sessions.
Common situations: Spiky load against an HTTP/2 server with strict idle timeouts; misconfigured `http2.maxConcurrentStreams` / pool size; holding a reference to an `h2session` and reusing it after it began closing; a misbehaving server sending GOAWAY aggressively.
Related errors
- HTTP/2 pseudo-headers are not supported in `options.headers`
- Protocol "${this.protocol}" not supported. Expected "https:"
AI-assisted analysis of sindresorhus/got@e3924aa1e5 (2026-08-03).
Data as JSON: /data/errors/6ba8814219d601ad.json.
Report an issue: GitHub.