feat(copilot): add 401 auth recovery with automatic token refresh and client rebuild#10179
Closed
l0hde wants to merge 1 commit into
Closed
feat(copilot): add 401 auth recovery with automatic token refresh and client rebuild#10179l0hde wants to merge 1 commit into
l0hde wants to merge 1 commit into
Conversation
… 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.
This was referenced Apr 24, 2026
Collaborator
Contributor
|
Thanks for this contribution, @l0hde! The 401 auth recovery you implemented here was cherry-picked (with full attribution) into the consolidated Copilot salvage PR #15114, which merged on 2026-04-24. This is an automated hermes-sweeper review. Evidence that this is already on
Closing as implemented on main. |
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
When using GitHub Copilot as the provider, HTTP 401 errors cause Hermes to silently fall back to the next model in the chain instead of recovering. This PR adds a one-shot retry mechanism that re-resolves the Copilot token, rebuilds the OpenAI client, and retries the failed request before falling back.
Problem
Copilot OAuth tokens (
gho_*) are typically long-lived and valid, but the httpx client state can become stale — particularly after startup race conditions or in long-lived sessions. When this happens, the API returns 401, and Hermes drops to a fallback model without attempting recovery.Other providers (Codex, Nous, Anthropic) already have 401 recovery handlers. Copilot was missing one.
Changes
run_agent.py_try_refresh_copilot_client_credentials()COPILOT_GITHUB_TOKEN→GH_TOKEN→GITHUB_TOKEN→gh auth token)api_keyand_client_kwargs_apply_client_headers_for_base_url()_replace_primary_openai_client()copilot_auth_retry_attempted = False(matches existing pattern for other providers)provider == "copilot", calls refresh, retries on successtests/run_agent/test_run_agent_codex_responses.pytest_run_conversation_copilot_refreshes_after_401_and_retries— end-to-end 401→refresh→retrytest_try_refresh_copilot_client_credentials_rebuilds_client— unit test for the refresh methodtest_try_refresh_copilot_client_credentials_rebuilds_even_if_token_unchanged— verifies rebuild happens even when token string is identical (stale client state recovery)website/docs/integrations/providers.md/copilot_internal/v2/tokenis not used (returns 404 for some account types)Design Decisions
gho_*token is often stable across refreshes. The 401 recovery is about rebuilding stale httpx client state, not necessarily getting a new token._apply_client_headers_for_base_url(): Canonical way to set provider-specific headers (Editor-Version, Copilot-Integration-Id, etc.) — more maintainable than manual header construction./copilot_internal/v2/tokenreturns 404 for some account types. Directgho_*auth works reliably and matches approaches used by other editors (e.g., Zed).Testing
All 46 tests pass: