Summary
The OpenCode transport plugin (headroom/providers/opencode/_dist/entry.opencode.js) sets HEADROOM_PROJECT as a spawned-subprocess env var only (shell.env hook, derived from input.project.id), but the fetch/http/https wrapping installed by installHeadroomTransport only ever sets x-headroom-base-url / x-headroom-original-path headers on routed requests. It never sets x-headroom-project.
Server-side, headroom/proxy/project_policy.py (classify_project) only attributes savings via the x-headroom-project header or a /p/<name> URL-path prefix. Since the OpenCode plugin never sends either on the actual proxied HTTP requests, every OpenCode request routed through Headroom is unattributed — the dashboard's "Per-Project Savings" panel permanently shows 0 project(s) for OpenCode users, even with --memory --memory-storage project running correctly (that's a separate, cwd-based attribution path used only for the memory DB, not for cost/savings attribution).
Environment
- headroom-ai 0.34.0
- OpenCode (current, plugin loaded via packaged
_dist/entry.opencode.js)
- macOS arm64
Reproduction
- Run
headroom proxy --memory --memory-storage project (or any profile with the OpenCode plugin active).
- Use OpenCode with the Headroom transport plugin loaded (
OPENCODE_CONFIG_CONTENT → plugin: [...entry.opencode.js]).
- Make several requests through the proxy.
- Open the Headroom dashboard → "Per-Project Savings" panel stays at
0 project(s) / "No per-project data yet." regardless of traffic volume.
Expected
OpenCode traffic routed through the proxy should be attributable per-project, same as Claude Code (/p/<name> base URL) or any client sending x-headroom-project.
Suggested fix
Resolve input.project.id (falling back to pluginOptions.project / input.directory) once at plugin init in HeadroomPlugin, thread it through installHeadroomTransport's state, and set an x-headroom-project header alongside the existing x-headroom-base-url header in both header-construction seams:
mergeFetchHeaders (used by the wrapped fetch)
headersForNodeRequest (used by the wrapped http.request/https.request)
I have a working patch against the packaged _dist/entry.opencode.js (adds a project parameter threaded through installHeadroomTransport → wrapRequest/routedNodeOptions → headersForNodeRequest, and through the fetch wrapper → withRoutedFetchInput → mergeFetchHeaders) and can share a diff/PR if useful.
Related but distinct issues
Summary
The OpenCode transport plugin (
headroom/providers/opencode/_dist/entry.opencode.js) setsHEADROOM_PROJECTas a spawned-subprocess env var only (shell.envhook, derived frominput.project.id), but thefetch/http/httpswrapping installed byinstallHeadroomTransportonly ever setsx-headroom-base-url/x-headroom-original-pathheaders on routed requests. It never setsx-headroom-project.Server-side,
headroom/proxy/project_policy.py(classify_project) only attributes savings via thex-headroom-projectheader or a/p/<name>URL-path prefix. Since the OpenCode plugin never sends either on the actual proxied HTTP requests, every OpenCode request routed through Headroom is unattributed — the dashboard's "Per-Project Savings" panel permanently shows0 project(s)for OpenCode users, even with--memory --memory-storage projectrunning correctly (that's a separate, cwd-based attribution path used only for the memory DB, not for cost/savings attribution).Environment
_dist/entry.opencode.js)Reproduction
headroom proxy --memory --memory-storage project(or any profile with the OpenCode plugin active).OPENCODE_CONFIG_CONTENT→plugin: [...entry.opencode.js]).0 project(s)/ "No per-project data yet." regardless of traffic volume.Expected
OpenCode traffic routed through the proxy should be attributable per-project, same as Claude Code (
/p/<name>base URL) or any client sendingx-headroom-project.Suggested fix
Resolve
input.project.id(falling back topluginOptions.project/input.directory) once at plugin init inHeadroomPlugin, thread it throughinstallHeadroomTransport's state, and set anx-headroom-projectheader alongside the existingx-headroom-base-urlheader in both header-construction seams:mergeFetchHeaders(used by the wrappedfetch)headersForNodeRequest(used by the wrappedhttp.request/https.request)I have a working patch against the packaged
_dist/entry.opencode.js(adds aprojectparameter threaded throughinstallHeadroomTransport→wrapRequest/routedNodeOptions→headersForNodeRequest, and through thefetchwrapper →withRoutedFetchInput→mergeFetchHeaders) and can share a diff/PR if useful.Related but distinct issues
headroom wrap codexreturns 403 for WebSocket transport when using/p/<project>routing #2355 —/p/<project>WebSocket 403 forheadroom wrap codex(a routing bug on an already-attributed path, not this gap)hook-shim/handler.jsfile (separate bundling bug, unrelated to project attribution)