Skip to content

feat(copilot): add 401 auth recovery with automatic token refresh and client rebuild#10179

Closed
l0hde wants to merge 1 commit into
NousResearch:mainfrom
l0hde:feature/copilot-401-recovery
Closed

feat(copilot): add 401 auth recovery with automatic token refresh and client rebuild#10179
l0hde wants to merge 1 commit into
NousResearch:mainfrom
l0hde:feature/copilot-401-recovery

Conversation

@l0hde

@l0hde l0hde commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

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

  • New method: _try_refresh_copilot_client_credentials()
    • Re-resolves token via the standard priority chain (COPILOT_GITHUB_TOKENGH_TOKENGITHUB_TOKENgh auth token)
    • Updates api_key and _client_kwargs
    • Reapplies Copilot-specific headers via _apply_client_headers_for_base_url()
    • Rebuilds the shared OpenAI client via _replace_primary_openai_client()
  • New retry guard: copilot_auth_retry_attempted = False (matches existing pattern for other providers)
  • New 401 handler in the error recovery loop: checks provider == "copilot", calls refresh, retries on success

tests/run_agent/test_run_agent_codex_responses.py

  • test_run_conversation_copilot_refreshes_after_401_and_retries — end-to-end 401→refresh→retry
  • test_try_refresh_copilot_client_credentials_rebuilds_client — unit test for the refresh method
  • test_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

  • New info section documenting Copilot auth behavior: direct-token auth, 401 recovery flow, and why token exchange via /copilot_internal/v2/token is not used (returns 404 for some account types)

Design Decisions

  • Always rebuild client even if token unchanged: The gho_* token is often stable across refreshes. The 401 recovery is about rebuilding stale httpx client state, not necessarily getting a new token.
  • Uses _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.
  • One-shot flag guard: Prevents infinite 401 loops. Matches the existing pattern used by Codex, Nous, and Anthropic providers exactly.
  • No token exchange endpoint: /copilot_internal/v2/token returns 404 for some account types. Direct gho_* auth works reliably and matches approaches used by other editors (e.g., Zed).

Testing

All 46 tests pass:

pytest tests/run_agent/test_run_agent_codex_responses.py -q
46 passed in 9.96s

… 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.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have provider/copilot GitHub Copilot (ACP + Chat) comp/agent Core agent loop, run_agent.py, prompt builder labels Apr 26, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Likely superseded by #15114 (merged) — that PR already added Copilot token exchange + 401 auth recovery with client rebuild. Please check if this PR adds anything not covered by #15114.

@teknium1

Copy link
Copy Markdown
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 main:

Closing as implemented on main.

@teknium1 teknium1 closed this Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P3 Low — cosmetic, nice to have provider/copilot GitHub Copilot (ACP + Chat) type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants