{"record":{"id":"833d9d33eb7febd3","repo":"headroomlabs-ai/headroom","slug":"headroom-opencode-wrap-blocked-direct-http-2-conne","errorCode":null,"errorMessage":"Headroom OpenCode wrap blocked direct HTTP/2 connection to ${upstream.origin}. Use fetch, http, or https so traffic can be routed through Headroom.","messagePattern":"Headroom OpenCode wrap blocked direct HTTP/2 connection to (.+?)\\. Use fetch, http, or https so traffic can be routed through Headroom\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"headroom/providers/opencode/hook-shim/handler.js","lineNumber":305,"sourceCode":"    const nextArgs = parts.callback ? [nextOptions, parts.callback] : [nextOptions];\n    return Reflect.apply(targetRequest, this, nextArgs);\n  };\n}\nfunction wrapGet(request) {\n  return function headroomGet(...args) {\n    const req = Reflect.apply(request, this, args);\n    req.end();\n    return req;\n  };\n}\nfunction wrapHttp2Connect(originalConnect) {\n  return function headroomHttp2Connect(authority, ...args) {\n    const state = getState();\n    if (state) {\n      const proxy = normalizeProxyUrl(state.proxyUrl);\n      const upstream = authority instanceof URL ? authority : new URL(String(authority));\n      if (shouldRoute(upstream, proxy)) {\n        throw new Error(\n          `Headroom OpenCode wrap blocked direct HTTP/2 connection to ${upstream.origin}. Use fetch, http, or https so traffic can be routed through Headroom.`\n        );\n      }\n    }\n    return Reflect.apply(originalConnect, this, [authority, ...args]);\n  };\n}\nfunction installHeadroomTransport(options) {\n  const existing = getState();\n  if (existing) {\n    existing.refs += 1;\n    existing.proxyUrl = options.proxyUrl;\n    existing.debug = Boolean(options.debug);\n    installProcessEnv(options.proxyUrl);\n    return () => uninstallHeadroomTransport();\n  }\n  const state = {\n    refs: 1,","sourceCodeStart":287,"sourceCodeEnd":323,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/providers/opencode/hook-shim/handler.js#L287-L323","documentation":"Headroom's OpenCode hook shim monkey-patches Node's http2.connect. When OpenCode (or a dependency) opens a raw HTTP/2 session to an origin that shouldRoute() says belongs behind the Headroom proxy, the shim throws immediately instead of letting the connection bypass the proxy. This is intentional enforcement: traffic to routed upstreams must go through fetch/http/https, which the shim can transparently redirect to HEADROOM_OPENCODE_TRANSPORT_PROXY_URL.","triggerScenarios":"Inside a process with the Headroom hook-shim preloaded, any code path calling http2.connect(authority) where authority's origin matches a routed upstream (shouldRoute(upstream, proxy) is true) — e.g. an SDK like @anthropic-ai/sdk or google-auth using HTTP/2 directly to api.anthropic.com or api.openai.com.","commonSituations":"An OpenCode plugin or MCP client switching to an HTTP/2-based transport (gRPC-style, node http2 core module) while running under `headroom opencode` wrapping; a dependency upgrade that moved from fetch/https to http2.connect; the proxy URL being set so broadly that every origin counts as routed.","solutions":["Refactor the calling code to use fetch(), http.request, or https.request — those are wrapped and will be transparently routed through Headroom.","If the direct HTTP/2 connection is legitimate and must not be proxied, narrow the routing scope so shouldRoute() returns false for that origin (configure the shim's proxy routing / bypass list accordingly).","If you did not intend to run under Headroom at all, remove the shim preload (NODE_OPTIONS --require / import hook) from the process environment."],"exampleFix":"// before\nconst session = http2.connect(\"https://api.anthropic.com\"); // throws under shim\n\n// after\nconst res = await fetch(\"https://api.anthropic.com/v1/messages\", {...}); // routed through Headroom","handlingStrategy":"try-catch","validationCode":"// Before any http2 use under the shim, check routing intent:\nimport http2 from \"node:http2\";\n// no public shouldRoute export — instead probe with a sentinel try/catch once at startup (see tryCatchPattern).","typeGuard":null,"tryCatchPattern":"function safeHttp2Connect(authority: string, ...args: unknown[]) {\n  try {\n    return http2.connect(authority, ...args);\n  } catch (err) {\n    if (err instanceof Error && err.message.includes(\"blocked direct HTTP/2 connection\")) {\n      // fall back to a routed transport\n      return routeViaFetch(new URL(authority));\n    }\n    throw err;\n  }\n}","preventionTips":["Prefer fetch() for provider HTTP under Headroom-wrapped runtimes; avoid node:http2 for API upstreams.","When adding an SDK dependency under `headroom opencode`, check whether it uses http2.connect (grep node_modules) before enabling it.","Keep the shim's routing scope narrow so non-API origins can still use raw HTTP/2."],"tags":["proxy","nodejs","http2","opencode","monkey-patching"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}