{"record":{"id":"0acef88a2fba8347","repo":"denoland/deno","slug":"err-http2-headers-sent-0acef8","errorCode":"ERR_HTTP2_HEADERS_SENT","errorMessage":"Response has already been initiated.","messagePattern":"Response has already been initiated\\.","errorType":"exception","errorClass":"NodeError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/internal/http2/compat.js","lineNumber":657,"sourceCode":"  getHeaderNames() {\n    return ObjectKeys(this[kHeaders]);\n  }\n\n  getHeaders() {\n    const headers = { __proto__: null };\n    return ObjectAssign(headers, this[kHeaders]);\n  }\n\n  hasHeader(name) {\n    validateString(name, \"name\");\n    name = StringPrototypeToLowerCase(StringPrototypeTrim(name));\n    return ObjectHasOwn(this[kHeaders], name);\n  }\n\n  removeHeader(name) {\n    validateString(name, \"name\");\n    if (this[kStream].headersSent) {\n      throw new ERR_HTTP2_HEADERS_SENT();\n    }\n\n    name = StringPrototypeToLowerCase(StringPrototypeTrim(name));\n\n    if (name === \"date\") {\n      this[kState].sendDate = false;\n\n      return;\n    }\n\n    delete this[kHeaders][name];\n  }\n\n  setHeader(name, value) {\n    validateString(name, \"name\");\n    if (this[kStream].headersSent) {\n      throw new ERR_HTTP2_HEADERS_SENT();\n    }","sourceCodeStart":639,"sourceCodeEnd":675,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/internal/http2/compat.js#L639-L675","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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"],"exampleFix":"// before\ntry { res.end(body); } finally { res.removeHeader(\"x-debug\"); } // throws\n\n// after\nres.removeHeader(\"x-debug\"); // mutate before any write\nres.end(body);","handlingStrategy":"validation","validationCode":"function safeRemoveHeader(res, name) {\n  if (!res.headersSent) res.removeHeader(name);\n}","typeGuard":"function canMutateHeaders(res: http2.Http2ServerResponse): boolean {\n  return !res.headersSent;\n}","tryCatchPattern":"try {\n  res.removeHeader(name);\n} catch (err) {\n  if (err.code === \"ERR_HTTP2_HEADERS_SENT\") return; // too late; ignore\n  throw err;\n}","preventionTips":["Centralize header mutations before the first body write","Wrap header mutation helpers in headersSent guards","Treat headersSent === true as 'response committed: no header edits'"],"tags":["http2","headers","response-lifecycle"],"backgroundTag":"headers-already-sent","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}