Skip to content

fix(copilot): route enterprise Copilot seats to the right endpoint and host - #93

Merged
Bhasvanth-Dev9380 merged 1 commit into
mainfrom
fix/copilot-enterprise-421
Aug 7, 2026
Merged

fix(copilot): route enterprise Copilot seats to the right endpoint and host#93
Bhasvanth-Dev9380 merged 1 commit into
mainfrom
fix/copilot-enterprise-421

Conversation

@Bhasvanth-Dev9380

Copy link
Copy Markdown
Contributor

The bug

A GitHub Copilot Business/Enterprise login failed every turn with 421 Misdirected Request, whatever the prompt was.

An enterprise seat's /models list carries ids Pi's bundled catalog doesn't have yet (the gpt-5.6-* family). The never-miss resolver synthesizes an unknown id by cloning a catalogued model of the same provider — but it cloned the first one, claude-fable-5, an openai-completions entry. Copilot serves GPT-5+/codex only on /responses, so every request went to /chat/completions and the edge answered 421.

Two aggravating factors on the same path:

  • the bundled catalog hardcodes api.individual.githubcopilot.com, but a business/enterprise token must use its own host (encoded in the token's proxy-ep);
  • 421 classified as unknown — transient — so the loop burned three identical retries and surfaced last reason=unknown.

The approach

GitHub advertises no endpoint metadata in /models (github/copilot-cli#4337), so endpoint selection resolves newest evidence first:

  1. learned — what the live API actually accepted for this id
  2. advertised — an endpoint list on the account's own /models entry, parsed defensively so it activates itself if GitHub ever ships the field
  3. family — the id-family rule, which is what Pi's catalog encodes and what GitHub's own clients assume

Changes

  • new github-copilot-transport.ts — endpoint + host + editor-header correction, applied at one choke point in the agent loop so the catalogued and synthesized paths are both covered
  • self-healing stream wrapper — an endpoint rejection before any event is emitted re-issues once on the other surface and is remembered, so a model family shipped after this release routes itself. Bounded to Copilot, once per stream, never after partial output
  • host derived from the live token's proxy-ep, covering individual/business/enterprise and the GHE copilot-proxy.copilot-api. shape; a deliberately configured gateway baseUrl is left alone
  • synthesized Copilot ids take context window + vision from the account's own catalog instead of a template's
  • template selection is family-matched for every provider: gpt-5.6-sol clones gpt-5.5, not the provider's first-listed model
  • 421 and "not accessible via the /… endpoint" classify as model_not_found: one fast failure with an actionable message, and the fallback chain gets its turn

Verification

  • npm run build clean; tsc --noEmit clean on both configs
  • 708 agents + integrations tests pass, 21 of them new (github-copilot-routing.test.ts, plus 6 classifier cases)
  • covered: endpoint rule per family, host derivation incl. GHE, family-matched templates, the heal wrapper (re-issue + learn, rethrow on unrelated errors, never replay after partial output, pass-through for other providers)

References

…d host

A GitHub Copilot Business/Enterprise login failed every turn with
`421 Misdirected Request`, whatever the prompt was.

Root cause: an enterprise seat's `/models` list carries ids Pi's bundled
catalog doesn't have yet (the `gpt-5.6-*` family). The never-miss resolver
synthesizes an unknown id by cloning a catalogued model of the same
provider — but it cloned the FIRST one, `claude-fable-5`, an
`openai-completions` entry. Copilot serves GPT-5+/codex only on
`/responses`, so every request went to `/chat/completions` and the edge
answered 421. Two aggravating factors on the same path: the bundled
catalog hardcodes `api.individual.githubcopilot.com` (a business or
enterprise token must use its own host), and 421 classified as `unknown`
— transient — so the loop burned three identical retries and surfaced
"last reason=unknown".

GitHub advertises no endpoint metadata in `/models`
(github/copilot-cli#4337), so endpoint selection resolves newest-evidence
first: learned (what the live API accepted) -> advertised (parsed
defensively, so it activates itself if GitHub ever ships the field) ->
id family (what Pi's catalog encodes and GitHub's own clients assume).

- new `github-copilot-transport.ts`: endpoint + host + editor-header
  correction, applied at one choke point in the agent loop so the
  catalogued and synthesized paths are both covered
- self-healing stream wrapper: an endpoint rejection before any event is
  emitted re-issues once on the other surface and is remembered, so a
  model family shipped after this release routes itself. Bounded to
  Copilot, once per stream, never after partial output
- host derived from the live token's `proxy-ep`, covering
  individual/business/enterprise and the GHE `copilot-proxy.` shape; a
  deliberately configured gateway baseUrl is left alone
- synthesized Copilot ids take context window + vision from the account's
  own catalog instead of a template's
- template selection is family-matched for every provider: `gpt-5.6-sol`
  clones `gpt-5.5`, not the provider's first-listed model
- 421 and "not accessible via the /… endpoint" classify as
  model_not_found: one fast failure with an actionable message, and the
  fallback chain gets its turn
@Bhasvanth-Spinabot
Bhasvanth-Spinabot self-requested a review August 7, 2026 16:35
@Bhasvanth-Dev9380
Bhasvanth-Dev9380 merged commit 289ee1d into main Aug 7, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants