Skip to content

api-proxy: inject X-Initiator: agent default on all Copilot-bound requests to prevent billing inflation#2571

Merged
lpcox merged 10 commits intomainfrom
copilot/fix-billing-header-injection
May 5, 2026
Merged

api-proxy: inject X-Initiator: agent default on all Copilot-bound requests to prevent billing inflation#2571
lpcox merged 10 commits intomainfrom
copilot/fix-billing-header-injection

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 5, 2026

In BYOK/api-proxy mode, absent X-Initiator headers cause the Copilot API to bill every request as a user-initiated premium turn (~14x cost vs. the expected ~7% user / ~93% agent split in agentic workflows).

Changes

  • containers/api-proxy/server.js — In proxyRequest(), after assembling outgoing headers, inject X-Initiator: agent for all Copilot-bound requests when the client hasn't supplied the header. The check uses targetHost.endsWith('githubcopilot.com') rather than the provider name so that OpenCode requests delegated to the Copilot backend are also covered (both api.githubcopilot.com and api.enterprise.githubcopilot.com). Client-supplied values are passed through unchanged, preserving correct behaviour for standard Copilot CLI flows.
if (targetHost.endsWith('githubcopilot.com') && !headers['x-initiator']) {
  headers['x-initiator'] = 'agent';
}
  • containers/api-proxy/server.test.js — Seven new tests covering: header injected when absent on direct Copilot requests, header injected on enterprise Copilot host, header injected when OpenCode routes to the Copilot backend (provider='opencode', Copilot target host), client-set value preserved on direct Copilot and OpenCode→Copilot requests, no injection for non-Copilot providers, and no injection when OpenCode routes to a non-Copilot backend.

@lpcox lpcox marked this pull request as ready for review May 5, 2026 15:11
Copilot AI review requested due to automatic review settings May 5, 2026 15:11
@lpcox lpcox requested a review from Mossaka as a code owner May 5, 2026 15:11
Copilot AI changed the title [WIP] Fix api-proxy to inject X-Initiator billing header api-proxy: inject X-Initiator: agent default on Copilot requests to prevent billing inflation May 5, 2026
Copilot finished work on behalf of lpcox May 5, 2026 15:12
Copilot AI requested a review from lpcox May 5, 2026 15:12
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 86.45% 86.53% 📈 +0.08%
Statements 86.38% 86.46% 📈 +0.08%
Functions 90.61% 90.61% ➡️ +0.00%
Branches 79.23% 79.27% 📈 +0.04%
📁 Per-file Coverage Changes (2 files)
File Lines (Before → After) Statements (Before → After)
src/container-cleanup.ts 77.3% → 77.5% (+0.12%) 77.0% → 77.1% (+0.11%)
src/container-lifecycle.ts 86.1% → 87.2% (+1.09%) 86.3% → 87.4% (+1.08%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the api-proxy to add a default X-Initiator: agent header on Copilot HTTP requests and adds unit tests around that behavior. In the broader api-proxy architecture, it changes request forwarding behavior in the shared proxy layer and extends the main server test suite.

Changes:

  • Add default x-initiator injection in proxyRequest() for requests identified as Copilot.
  • Preserve an existing client-supplied x-initiator value instead of overwriting it.
  • Add tests covering direct Copilot injection, preservation, and non-Copilot no-op behavior.
Show a summary per file
File Description
containers/api-proxy/server.js Adds the new x-initiator defaulting logic in the shared HTTP proxy path.
containers/api-proxy/server.test.js Imports proxyRequest and adds unit tests for the new header-injection behavior.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread containers/api-proxy/server.js Outdated
Comment on lines +323 to +326
// Default X-Initiator to "agent" for billing purposes on Copilot requests.
// In agentic workflows, the vast majority of requests are agent-initiated.
// If the client already set it (e.g. standard Copilot CLI), respect that value.
if (provider === 'copilot' && !headers['x-initiator']) {
Comment thread containers/api-proxy/server.test.js Outdated
Comment on lines +2840 to +2845
it('does not inject x-initiator for non-copilot providers', () => {
const { getCaptured } = mockHttpsRequest();
const req = makeReq();
proxyRequest(req, makeRes(), 'api.anthropic.com', { 'x-api-key': 'sk-ant-test' }, 'anthropic');
req.emit('end');
expect(getCaptured().headers['x-initiator']).toBeUndefined();
@github-actions

This comment has been minimized.

@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented May 5, 2026

@copilot address the review feedback

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 5, 2026

@copilot address the review feedback

Done in cdccf31. Changed the check from provider === 'copilot' to targetHost.endsWith('githubcopilot.com') so the header is injected regardless of which adapter name is in play — this covers both direct Copilot requests and the OpenCode→Copilot routing path (including api.enterprise.githubcopilot.com). Added regression tests for the OpenCode→Copilot case and for OpenCode→non-Copilot (verifying no injection).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • api.example.com
    • Triggering command: /opt/hostedtoolcache/node/20.20.2/x64/bin/node node /home/REDACTED/work/gh-aw-firewall/gh-aw-firewall/containers/api-proxy/node_modules/.bin/jest --verbose --ci server.test.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title api-proxy: inject X-Initiator: agent default on Copilot requests to prevent billing inflation api-proxy: inject X-Initiator: agent default on all Copilot-bound requests to prevent billing inflation May 5, 2026
@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Smoke Test: GitHub Actions Services Connectivity

Check Result
Redis PING ❌ timeout/no response
PostgreSQL pg_isready ❌ no response
PostgreSQL SELECT 1 ❌ connection failed

Overall: FAILhost.docker.internal was not reachable on ports 6379 or 5432 from this runner environment.

🔌 Service connectivity validated by Smoke Services

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

…aw post-run

The gh-aw post-run audit step runs as the 'runner' user but AWF creates
audit files as root with mode 0600. When AWF cleanup is interrupted
(SIGKILL from timeout), the chmod a+rX never runs, leaving files
unreadable and causing:

  EACCES: permission denied, stat 'docker-compose.redacted.yml'

Fix: Create audit files with mode 0644 (dir 0755) from the start. These
files contain no secrets — squid.conf has only domain ACLs, compose is
redacted, and policy-manifest is just firewall rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

lpcox and others added 2 commits May 5, 2026 10:09
With --build-local, Docker image builds take 7+ minutes on cold cache
(no layer cache on CI runners). The 5-minute step timeout causes the
Copilot CLI execution step to be killed before the agent even starts.

Increase timeout-minutes from 5 to 15 in:
- smoke-copilot-byok (md + lock)
- smoke-copilot (md + lock)
- smoke-gemini (md only, lock was already 15)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Pin gh-aw extension to v0.71.4
- Add sandbox.agent.id: awf to all workflows (new strict mode requirement)
- Remove empty mcp: field from update-release-notes.md (codemod artifact)
- Run post-processing on all lock files

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🔥 Smoke Test: Copilot BYOK (Offline) Mode

Test Result
GitHub MCP (list PRs) ✅ PR #2538 returned
GitHub.com HTTP ⚠️ Pre-step output not expanded (template variable unresolved)
File write/read ⚠️ Pre-step output not expanded (template variable unresolved)
BYOK inference (api-proxy → api.githubcopilot.com) ✅ Responding via BYOK offline mode

Running in BYOK offline mode (COPILOT_OFFLINE=true) via api-proxy → api.githubcopilot.com

PR author: @Copilot · Assignees: @lpcox, @Copilot

Overall: PARTIAL — BYOK inference and MCP ✅; pre-step smoke data unexpanded in template.

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🔬 Smoke Test Results

Test Status
GitHub MCP connectivity
GitHub.com HTTP connectivity ⚠️ template not expanded
File write/read ⚠️ template not expanded

PR: api-proxy: inject X-Initiator: agent default on all Copilot-bound requests to prevent billing inflation
Author: @Copilot | Assignees: @lpcox, @Copilot

Overall: PARTIAL — MCP ✅, pre-computed test data unavailable (workflow template vars not substituted).

📰 BREAKING: Report filed by Smoke Copilot

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Smoke Test Results:
✅ GitHub MCP Testing (last 2 merged PRs reviewed)
✅ Playwright Testing (github.com title verification)
✅ File Writing Testing (temp file creation)
✅ Bash Tool Testing (file verification)

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx all passed ✅ PASS
Node.js execa all passed ✅ PASS
Node.js p-limit all passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #2571 · ● 1.5M ·

@github-actions

This comment has been minimized.

@github-actions github-actions Bot mentioned this pull request May 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🧪 Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3 ❌ NO
Node.js v24.14.1 v20.20.2 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall: ❌ Not all tests passed. Python and Node.js versions differ between host and chroot.

Tested by Smoke Chroot

@lpcox lpcox merged commit e61f158 into main May 5, 2026
60 of 67 checks passed
@lpcox lpcox deleted the copilot/fix-billing-header-injection branch May 5, 2026 17:54
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Codex Smoke Test

api-proxy: inject X-Initiator: agent default on all Copilot-bound requests to prevent billing inflation
fix: one-shot-token.so fails to load on musl/Alpine hosts (ARC runners)
GitHub PR review ✅ | SafeInputs GH ❌ | Playwright ✅ | Tavily ❌ | File/Bash ✅ | Discussion ✅ | Build ✅
Overall status: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

api-proxy should inject X-Initiator billing header to prevent premium over-billing in BYOK mode

4 participants