fix(copilot): token exchange + 401 auth recovery + live context + ACP HOME env#15114
Merged
Conversation
Pass an explicit HOME into Copilot ACP child processes so delegated ACP runs do not fail when the ambient environment is missing HOME.
Prefer the per-profile subprocess home when available, then fall back to HOME, expanduser('~'), pwd.getpwuid(...), and /home/openclaw. Add regression tests for both profile-home preference and clean HOME fallback.
Refs #11068.
… client rebuild When using GitHub Copilot as provider, HTTP 401 errors could cause Hermes to silently fall back to the next model in the chain instead of recovering. This adds a one-shot retry mechanism that: 1. Re-resolves the Copilot token via the standard priority chain (COPILOT_GITHUB_TOKEN -> GH_TOKEN -> GITHUB_TOKEN -> gh auth token) 2. Rebuilds the OpenAI client with fresh credentials and Copilot headers 3. Retries the failed request before falling back The fix handles the common case where the gho_* OAuth token remains valid but the httpx client state becomes stale (e.g. after startup race conditions or long-lived sessions). Key design decisions: - Always rebuild client even if token string unchanged (recovers stale state) - Uses _apply_client_headers_for_base_url() for canonical header management - One-shot flag guard prevents infinite 401 loops (matches existing pattern used by Codex/Nous/Anthropic providers) - No token exchange via /copilot_internal/v2/token (returns 404 for some account types; direct gho_* auth works reliably) Tests: 3 new test cases covering end-to-end 401->refresh->retry, client rebuild verification, and same-token rebuild scenarios. Docs: Updated providers.md with Copilot auth behavior section.
Raw GitHub tokens (gho_/github_pat_/ghu_) are now exchanged for short-lived Copilot API tokens via /copilot_internal/v2/token before being used as Bearer credentials. This is required to access internal-only models (e.g. claude-opus-4.6-1m with 1M context). Implementation: - exchange_copilot_token(): calls the token exchange endpoint with in-process caching (dict keyed by SHA-256 fingerprint), refreshed 2 minutes before expiry. No disk persistence — gateway is long-running so in-memory cache is sufficient. - get_copilot_api_token(): convenience wrapper with graceful fallback — returns exchanged token on success, raw token on failure. - Both callers (hermes_cli/auth.py and agent/credential_pool.py) now pipe the raw token through get_copilot_api_token() before use. 12 new tests covering exchange, caching, expiry, error handling, fingerprinting, and caller integration. All 185 existing copilot/auth tests pass. Part 2 of #7731.
… resolver The Copilot provider resolved context windows via models.dev static data, which does not include account-specific models (e.g. claude-opus-4.6-1m with 1M context). This adds the live Copilot /models API as a higher- priority source for copilot/copilot-acp/github-copilot providers. New helper get_copilot_model_context() in hermes_cli/models.py extracts capabilities.limits.max_prompt_tokens from the cached catalog. Results are cached in-process for 1 hour. In agent/model_metadata.py, step 5a queries the live API before falling through to models.dev (step 5b). This ensures account-specific models get correct context windows while standard models still have a fallback. Part 1 of #7731. Refs: #7272
This was referenced Apr 24, 2026
tunings-lab
added a commit
to tunings-lab/hermes-agent
that referenced
this pull request
Jun 10, 2026
feat(copilot): consolidate GHE enterprise support on top of upstream NousResearch#15114
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidated salvage of 4 GitHub Copilot PRs — token exchange, 401 auth recovery, live context-length resolution, and HOME env propagation for ACP subprocesses. Attribution preserved via rebase-merge.
Changes
hermes_cli/copilot_auth.py+hermes_cli/auth.py+agent/credential_pool.py— addexchange_copilot_token()which callsGET https://api.github.com/copilot_internal/v2/tokento trade a raw GitHub token for a short-lived Copilot API token (the semicolon-separatedtid=...;exp=...;sku=...format). Previous code used the raw GitHub token asAuthorization: Bearer, which only works for some account types. Cherry-pick from @difujia (fix(copilot): exchange raw GitHub token for Copilot API JWT #12876).run_agent.py— add_try_refresh_copilot_client_credentials()paralleling the existing Nous/Anthropic/Codex refresh paths. On HTTP 401 from Copilot, refresh credentials and rebuild the OpenAI client instead of failing the request. Cherry-pick from @l0hde (feat(copilot): add 401 auth recovery with automatic token refresh and client rebuild #10179).agent/model_metadata.py+hermes_cli/models.py— probe Copilot's live/modelsendpoint formax_prompt_tokenson a per-account basis. Catches account-specific models (e.g.claude-opus-4.6-1m) that don't exist inmodels.dev, and returns the provider-enforced limit rather than the nominal marketing number. Cached 1h in-process. Cherry-pick from @difujia (fix(copilot): wire live /models max_prompt_tokens into context-window resolver #12840).agent/copilot_acp_client.py— passHOMEenv var into Copilot ACP subprocess. Without this, the subprocess inherits an empty or stale HOME and fails to locate config. Includes fallback chain:get_subprocess_home()→$HOME→expanduser("~")→ pwd lookup →/tmp. Cherry-pick from @MestreY0d4-Uninter (fix: set HOME for Copilot ACP subprocesses #11285), with/home/openclawhardcoded fallback replaced by/tmp.Credit
Validation
scripts/run_tests.sh tests/hermes_cli/test_copilot_token_exchange.py tests/hermes_cli/test_copilot_context.py tests/hermes_cli/test_copilot_auth.py tests/hermes_cli/test_copilot_in_model_list.py tests/agent/test_copilot_acp_client.py tests/agent/test_credential_pool.py tests/run_agent/test_run_agent_codex_responses.py tests/hermes_cli/test_model_switch_copilot_api_mode.py— 137/137 passing.All four PRs cherry-picked cleanly with no conflicts against current
main.Not included in this salvage
auth.py,config.py,main.py,models.py,run_agent.py. Will conflict with fix(copilot): exchange raw GitHub token for Copilot API JWT #12876's auth changes and needs a rebase on top of this salvage. Worth resubmitting as a focused PR after this lands.