## Description Adds a first-party `headroom copilot-auth login` flow for Copilot subscription mode and uses the resulting Copilot OAuth token to perform GitHub's Copilot token exchange before launching the wrapped Copilot CLI. This fixes Business/Enterprise Cloud accounts where a generic GitHub/Copilot token can read Copilot account metadata but is rejected by the Copilot token exchange endpoint. It also avoids treating GitHub.com Enterprise Cloud account URLs such as `github.com/enterprises/acme` as API hostnames. Fixes #635 Related: #488, #610 Builds on #576 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [x] Documentation update - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Adds `headroom copilot-auth login` and `headroom copilot-auth status`. - Stores a Headroom-specific Copilot OAuth token under Headroom's state dir. - Exchanges reusable Copilot OAuth tokens with Copilot Chat-compatible headers before subscription-mode launch. - Carries the resolved Copilot API endpoint into `headroom wrap copilot --subscription`. - Handles GitHub.com Enterprise Cloud URLs without synthesizing invalid `api.github.com/enterprises/...` hosts. - Adds focused unit tests and README guidance for subscription login. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```console ruff check headroom/copilot_auth.py headroom/cli/copilot_auth.py headroom/cli/main.py headroom/cli/__init__.py headroom/cli/wrap.py tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_cli/test_wrap_copilot.py tests/test_copilot_subscription_smoke.py # All checks passed! ruff format --check headroom/copilot_auth.py headroom/cli/copilot_auth.py headroom/cli/main.py headroom/cli/__init__.py headroom/cli/wrap.py tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_cli/test_wrap_copilot.py tests/test_copilot_subscription_smoke.py # 9 files already formatted python -m py_compile headroom/copilot_auth.py headroom/cli/copilot_auth.py headroom/cli/main.py headroom/cli/__init__.py headroom/cli/wrap.py tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_cli/test_wrap_copilot.py tests/test_copilot_subscription_smoke.py uv run --no-project --with pytest --with pytest-asyncio --with click --with rich --with opentelemetry-api --with pydantic --with tiktoken --with 'litellm==1.82.3' --with fastapi --with uvicorn --with 'httpx[http2]' --with openai --with mcp --with magika --with zstandard --with websockets --with onnxruntime --with transformers --with watchdog --with sqlite-vec pytest tests/test_copilot_auth.py tests/test_cli/test_copilot_auth.py tests/test_cli/test_wrap_copilot.py tests/test_cli_proxy_env.py tests/test_copilot_subscription_smoke.py # 127 passed ``` Local note: `uv run pytest ...` against the project currently fails before running tests because `uv.lock` has an unrelated `gitpython` wheel/version mismatch. ## Manual Validation I tested this with an existing GitHub Copilot Business subscription associated with a GitHub.com Enterprise Cloud account. The Enterprise Cloud value I tested was in the form: ```text github.com/enterprises/<enterprise> ``` The tested flow was: ```text headroom copilot-auth login headroom wrap copilot --subscription -- --model gpt-5.4 ``` This validated that Headroom does not treat github.com/enterprises/<enterprise> as a Copilot API hostname. Instead, token exchange uses GitHub.com and Headroom routes subscription-mode traffic to the Copilot API endpoint returned by GitHub for the signed-in account. I did not test this with GitHub Enterprise Server or a custom enterprise domain such as ghe.example.com. No tokens, request IDs, or organization-specific identifiers are included in this PR. ## Real Behavior Proof - Environment: macOS Darwin, Python 3.12.7, local checkout on `codex/copilot-business-auth`. - Exact command / steps: Ran `headroom copilot-auth login`, then launched `headroom wrap copilot --subscription -- --model gpt-5.4` with a GitHub Copilot Business subscription tied to a GitHub.com Enterprise Cloud account. - Observed result: Headroom did not treat `github.com/enterprises/<enterprise>` as a Copilot API hostname; token exchange used GitHub.com and subscription traffic was routed to the Copilot API endpoint returned for the signed-in account. The latest focused Copilot auth/proxy tests pass locally (`127 passed`). - Not tested: GitHub Enterprise Server or custom enterprise domains such as `ghe.example.com`; Windows Credential Manager integration still needs confirmation from someone on Windows. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing targeted unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Screenshots (if applicable) N/A ## Additional Notes Acknowledgement: the OAuth/token-exchange behavior was informed by `anomalyco/opencode-copilot-auth` by Aiden Cline. No tokens are printed by the new login/status commands; only a short SHA-256 fingerprint is displayed for troubleshooting. The interactive login is included because the missing piece is not just an Enterprise URL or routing hint. For GitHub.com Enterprise Cloud accounts, URLs like `github.com/enterprises/acme` identify the enterprise account but are not Copilot API hostnames; token exchange still happens through GitHub.com and then returns the account-specific Copilot API endpoint. A command-line enterprise argument can help for true GitHub Enterprise Server/custom-domain deployments, but it cannot produce the Copilot OAuth token class that the token-exchange endpoint accepts. Ideally, Headroom would avoid an extra interactive login and reuse an existing GitHub/Copilot CLI session everywhere. In practice, some reusable-looking tokens can read Copilot account metadata but are rejected by Copilot token exchange, which leaves Business/Enterprise Cloud users with missing model catalogs. The explicit login command is the smallest independent way to obtain and persist the token needed for that exchange without asking users to pass a secret on the command line. --------- Co-authored-by: jbelanger <your-username@users.noreply.github.com>
5.6 KiB
Contributing to Headroom
Thanks for contributing! Please skim this before opening a PR : the policies exist because we've been burned skipping them, not because we love paperwork.
By participating, you agree to our Code of Conduct.
Where does my contribution go?
| Type | What to do |
|---|---|
| 🐛 Bug or small fix | Open a PR (with repro + test) |
| ✨ New feature / architectural change | Open an issue or ask in Discord first. |
| 🧹 Refactor-only | Don't. Only if a maintainer asked, as part of a concrete fix. |
🧪 Test/CI-only PR chasing a known main failure |
Don't. We're tracking it. |
| 📦 New dep or version bump | PR with written justification. |
| ❓ Question | **Discord #help |
Open PR cap: 10 per author. Get existing ones merged before opening more.
Guiding principles
- Verification is the author's job, not the reviewer's.
- Supply chain is a real threat. Dependency changes get human review, every time.
Bug fixes
Every bug-fix PR must include:
- A reproduction — minimal code, failing test, or steps.
- A test that fails before your fix and passes after (unit, integration, or e2e).
If you genuinely can't write a test, say so explicitly and explain how you verified.
"Real behavior proof" — required on every external PR
We can't merge what we can't verify. Include a Real behavior proof section in the PR body covering:
- Setup you tested on (OS, Python, config, provider/model)
- Exact command or steps you ran after the patch
- After-fix evidence + observed result
- What you did not test
✅ Counts: screenshots, recordings, terminal output, copied live output, linked artifacts, redacted runtime logs. ❌ Does not count alone: unit tests, mocks, snapshots, lint, typechecks, green CI. Have them too — but they prove the test passes, not that the feature works.
PRs missing this may be autoclosed.
New features
Before writing code:
- Open a feature-request issue (or raise in Discord).
- Get a 👍 from a core maintainer before implementing.
- Include a short spec covering:
- API surface (public functions, config, CLI flags)
- Changes to existing behavior
- User stories — Given / When / Then, golden path + one edge case
- Failure modes
- Recovery / resilience
- Security considerations
Short and concrete beats long.
Dependencies & supply chain
A human maintainer reviews every dep change. PRs that add or bump a package must justify:
- Why this package (vs. doing it ourselves / using existing deps)
- Who maintains it (activity, release cadence, security history)
- Install surface (transitive deps, native code, install/runtime network)
- Why this version — permitted reasons: bug fix, security patch, required new functionality. Cosmetic bumps will be closed.
PR workflow
- Fork, branch from
main. - Install Node 18+ and run
uv sync --extra devthenmake install-git-hooks— installs repo pre-commit checks on every commit, commitlint on every commit message, and ci-precheck on every push. - One logical change per PR.
- Add tests.
uv run pytest·uv run ruff check .·uv run ruff format .- Update
CHANGELOG.mdfor user-facing changes. - Open the PR with a clear description +
Real behavior proof+ any spec/justification required, and keep the PR in draft until theReview Readinessboxes are complete.
Title format (conventional commits): feat:, fix:, docs:, test:, refactor:.
Commit message format is enforced locally by the repo's commit-msg hook and again in CI.
Review: CI green, one maintainer review, coverage held/improved.
Development setup
git clone https://github.com/chopratejas/headroom.git
cd headroom
python -m venv .venv && source .venv/bin/activate
node --version # Node 18+ required for commitlint hooks
python -m pip install --upgrade pip
python -m pip install -e ".[dev,relevance,proxy]"
python -m pytest
Headroom uses a pyproject.toml/maturin build backend. Older pip
versions may fail editable installs by looking for setup.py; upgrade pip
first or use uv sync --extra dev.
Dev Containers
Two configs ship for VS Code / Codespaces:
.devcontainer/devcontainer.json— Python 3.12,uv, Node.js,gh..devcontainer/memory-stack/devcontainer.json— adds Qdrant + Neo4j sidecars (useqdrant:6333,neo4j://neo4j:7687).
Inside, use: uv run ruff check ., uv run pytest, etc.
Optional automated review
This repository includes .github/copilot-instructions.md so maintainers can opt into GitHub Copilot code review without adding workflow billing noise to every PR.
Enable or disable automatic Copilot review in Settings → Rules → Rulesets → Automatically request Copilot code review. Keep it off unless maintainers explicitly want the extra review traffic.
Coding standards
- Ruff for lint + format, line length 100, PEP 8.
- Type hints on public functions; Google-style docstrings.
- Cover new behavior + edge cases; aim >80% coverage on new code.
- Python 3.10+. Optional features go behind extras.
Architecture principles
Safety first: never drop user/assistant content, never break tool call/response pairing, malformed content passes through unchanged, prefer false negatives.
Performance: transforms <50ms at P99, lazy-load optional deps, profile before optimizing.
Contributors are credited in CHANGELOG, the GitHub contributors page, and release notes. Thanks again. 💚