denoland/deno · error · NodeError
ERR_HTTP2_HEADERS_SENT
ERR_HTTP2_HEADERS_SENT
Error message
Response has already been initiated.
What it means
Http2ServerResponse.removeHeader throws ERR_HTTP2_HEADERS_SENT once the response headers have been serialized onto the h2 stream (stream.headersSent). After that point the header block has already been sent; removing a header is impossible.
Source
Thrown at ext/node/polyfills/internal/http2/compat.js:657
getHeaderNames() {
return ObjectKeys(this[kHeaders]);
}
getHeaders() {
const headers = { __proto__: null };
return ObjectAssign(headers, this[kHeaders]);
}
hasHeader(name) {
validateString(name, "name");
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
return ObjectHasOwn(this[kHeaders], name);
}
removeHeader(name) {
validateString(name, "name");
if (this[kStream].headersSent) {
throw new ERR_HTTP2_HEADERS_SENT();
}
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
if (name === "date") {
this[kState].sendDate = false;
return;
}
delete this[kHeaders][name];
}
setHeader(name, value) {
validateString(name, "name");
if (this[kStream].headersSent) {
throw new ERR_HTTP2_HEADERS_SENT();
}View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Guard every removal: if (!res.headersSent) res.removeHeader(name)
- Reorder middleware so all header mutations happen before the first write/end
- If headers already went out, the only options are finishing the response or destroying the stream — fix the ordering instead
Example fix
// before
try { res.end(body); } finally { res.removeHeader("x-debug"); } // throws
// after
res.removeHeader("x-debug"); // mutate before any write
res.end(body); Defensive patterns
Strategy: validation
Validate before calling
function safeRemoveHeader(res, name) {
if (!res.headersSent) res.removeHeader(name);
} Type guard
function canMutateHeaders(res: http2.Http2ServerResponse): boolean {
return !res.headersSent;
} Try / catch
try {
res.removeHeader(name);
} catch (err) {
if (err.code === "ERR_HTTP2_HEADERS_SENT") return; // too late; ignore
throw err;
} Prevention
- Centralize header mutations before the first body write
- Wrap header mutation helpers in headersSent guards
- Treat headersSent === true as 'response committed: no header edits'
When it happens
Trigger: Calling res.removeHeader("content-length") after res.write()/res.end()/writeHead() has begun the response; error handlers trying to strip headers after the body started; cleanup code in a finally block that runs after streaming starts.
Common situations: Late route errors after body streaming began; security filters removing internal headers (x-debug, x-internal-*) that run too late in the middleware chain; logging/observability middleware mutating headers on 'finish'.
Related errors
- ERR_INVALID_ARG_VALUE
- ERR_INVALID_HTTP_TOKEN
- ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED
- ERR_HTTP2_INVALID_HEADER_VALUE
- ERR_HTTP2_INVALID_PSEUDOHEADER
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/0acef88a2fba8347.
Report an issue: GitHub.