{"record":{"id":"18782dc2dbfb0d59","repo":"denoland/deno","slug":"err-http2-nested-push","errorCode":"ERR_HTTP2_NESTED_PUSH","errorMessage":"A push stream cannot initiate another push stream.","messagePattern":"A push stream cannot initiate another push stream\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/http2.ts","lineNumber":3090,"sourceCode":"  }\n\n  // True if the remote peer accepts push streams\n  get pushAllowed() {\n    return !this.destroyed &&\n      !this.closed &&\n      !this.session.closed &&\n      !this.session.destroyed &&\n      this[kSession].remoteSettings.enablePush;\n  }\n\n  // Create a push stream, call the given callback with the created\n  // Http2Stream for the push stream.\n  pushStream(headers, options, callback) {\n    if (!this.pushAllowed) {\n      throw new ERR_HTTP2_PUSH_DISABLED();\n    }\n    if (this[kID] % 2 === 0) {\n      throw new ERR_HTTP2_NESTED_PUSH();\n    }\n\n    const session = this[kSession];\n\n    debugStreamObj(this, \"initiating push stream\");\n\n    this[kUpdateTimer]();\n\n    if (typeof options === \"function\") {\n      callback = options;\n      options = undefined;\n    }\n\n    validateFunction(callback, \"callback\");\n\n    assertIsObject(options, \"options\");\n    options = { ...options };\n    options.endStream = !!options.endStream;","sourceCodeStart":3072,"sourceCodeEnd":3108,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/http2.ts#L3072-L3108","documentation":"pushStream() rejects nested pushes: streams initiated by a client have odd IDs and server-initiated push streams have even IDs, and the check this[kID] % 2 === 0 throws ERR_HTTP2_NESTED_PUSH when the initiating stream is itself a push stream ('A push stream cannot initiate another push stream'). This matches the HTTP/2 spec, where only the request stream may push.","triggerScenarios":"Calling pushStream inside the callback of a previous pushStream — e.g. a generic 'push associated resources' helper applied recursively to the stream it just received; passing the pushed stream from the pushStream callback into shared middleware that also pushes.","commonSituations":"Recursive asset-preload helpers (push index.html, then on the pushed stream push its CSS/JS); refactored request handlers that no longer distinguish the original request stream from a push stream; graph-walkers that push every discovered dependency.","solutions":["Only initiate pushStream from the original request stream (odd ID); never from inside a push callback","Pass the original stream (or its session + explicit flag) through your helper so pushes always root at the request","Flatten dependency graphs into one batch of pushes from the request stream instead of chaining"],"exampleFix":"// before\nstream.pushStream(indexHeaders, (err, pushStream) => {\n  pushStream.pushStream(cssHeaders, cb); // throws ERR_HTTP2_NESTED_PUSH\n});\n\n// after\nstream.pushStream(indexHeaders, cb);\nstream.pushStream(cssHeaders, cb); // both rooted at the request stream","handlingStrategy":"validation","validationCode":"const isRequestStream = (s: Http2Stream): boolean => s.id % 2 === 1;\nif (isRequestStream(stream)) {\n  stream.pushStream(headers, onPush); // only request streams may push\n}","typeGuard":"const isRequestStream = (s: Http2Stream & { id: number }): boolean =>\n  s.id % 2 === 1;","tryCatchPattern":"try {\n  pushStream.pushStream(headers, onPush);\n} catch (err) {\n  if ((err as NodeJS.ErrnoException).code === 'ERR_HTTP2_NESTED_PUSH') {\n    // re-root the push on the original request stream instead\n  } else throw err;\n}","preventionTips":["Pass the original request stream through helpers; never reuse the stream a push callback gives you as a push initiator","Flatten dependency pushes into one batch from the request stream","Guard push helpers with an odd-ID check so push streams can never recurse"],"tags":["http2","server-push","api-misuse","node-compat"],"backgroundTag":"http2-nested-push","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}