headroomlabs-ai/headroom · error · Error
Headroom OpenCode wrap blocked direct HTTP/2 connection to $
Error message
Headroom OpenCode wrap blocked direct HTTP/2 connection to ${upstream.origin}. Use fetch, http, or https so traffic can be routed through Headroom. What it means
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.
Source
Thrown at headroom/providers/opencode/hook-shim/handler.js:305
const nextArgs = parts.callback ? [nextOptions, parts.callback] : [nextOptions];
return Reflect.apply(targetRequest, this, nextArgs);
};
}
function wrapGet(request) {
return function headroomGet(...args) {
const req = Reflect.apply(request, this, args);
req.end();
return req;
};
}
function wrapHttp2Connect(originalConnect) {
return function headroomHttp2Connect(authority, ...args) {
const state = getState();
if (state) {
const proxy = normalizeProxyUrl(state.proxyUrl);
const upstream = authority instanceof URL ? authority : new URL(String(authority));
if (shouldRoute(upstream, proxy)) {
throw new Error(
`Headroom OpenCode wrap blocked direct HTTP/2 connection to ${upstream.origin}. Use fetch, http, or https so traffic can be routed through Headroom.`
);
}
}
return Reflect.apply(originalConnect, this, [authority, ...args]);
};
}
function installHeadroomTransport(options) {
const existing = getState();
if (existing) {
existing.refs += 1;
existing.proxyUrl = options.proxyUrl;
existing.debug = Boolean(options.debug);
installProcessEnv(options.proxyUrl);
return () => uninstallHeadroomTransport();
}
const state = {
refs: 1,View on GitHub (pinned to 322425c43b)
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.
Example fix
// before
const session = http2.connect("https://api.anthropic.com"); // throws under shim
// after
const res = await fetch("https://api.anthropic.com/v1/messages", {...}); // routed through Headroom Defensive patterns
Strategy: try-catch
Validate before calling
// Before any http2 use under the shim, check routing intent: import http2 from "node:http2"; // no public shouldRoute export — instead probe with a sentinel try/catch once at startup (see tryCatchPattern).
Try / catch
function safeHttp2Connect(authority: string, ...args: unknown[]) {
try {
return http2.connect(authority, ...args);
} catch (err) {
if (err instanceof Error && err.message.includes("blocked direct HTTP/2 connection")) {
// fall back to a routed transport
return routeViaFetch(new URL(authority));
}
throw err;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Headroom OpenCode transport shim loaded without HEADROOM_OPE
- Headroom OpenCode wrap blocked direct HTTP/2 connection to $
- failed to download {final_url} after {attempts} attempts: {e
- Error: Proxy dependencies not installed. Run: pip install he
- Shutting down...
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/833d9d33eb7febd3.
Report an issue: GitHub.