Visitar URL original
Compaction using Copilot Enterprise not possible · Issue #6768 · earendil-works/pi · GitHub
Skip to content

Compaction using Copilot Enterprise not possible #6768

Closed

Description

@MojangPlsFix

What happened?

If you compact context with PI using a Copilot Enterprise License you will get the following errors:

OpenAPI:
Error: Compaction failed: Turn prefix summarization failed: OpenAI API error (421): 421 Misdirected Request
Anthropic Models:
Error: Compaction failed: Turn prefix summarization failed: 421 Misdirected Request

Seems like Enterprise needs some extra headers according to this OpenClaw Issue I found

Steps to reproduce

  1. Use Model with Copilot Enterprise
  2. Fill up Context
  3. Compact

Expected behavior

Compaction should run through

Version

0.80.10

Activity

  1. github-actions commented on Jul 17, 2026

    @github-actions
    Contributor

    This issue was auto-closed. All issues from new contributors are auto-closed by default.

    Maintainers review auto-closed issues daily and reopen worthwhile ones. Issues that do not meet the quality bar in CONTRIBUTING.md will not be reopened or receive a reply.

    If a maintainer replies lgtmi on one of your issues, your future issues will stay open. If a maintainer replies lgtm, your future issues and PRs will stay open.

    See CONTRIBUTING.md.

  2. added
    untriagedThis issue has been auto closed and has not been triaged
    on Jul 17, 2026
  3. LoopControl commented on Jul 17, 2026

    @LoopControl

    I'm having same issue as well (Github Copilot).

    First experienced at around 500K/1M context but later on a new session hit same errors even at 58K/1M context.

  4. LoopControl commented on Jul 18, 2026

    @LoopControl

    This bug is making context grow unbounded (compaction not working) which means we have to create new sessions regularly.

    It looks like the baseUrl/hostname used for compaction on Enterprise accounts is wrong. It should be api.enterprise.githubcopilot.com but its being sent to api.individual.githubcopilot.com

  5. Marvae commented on Jul 19, 2026

    @Marvae
    Contributor

    I'm seeing this too. It looks like a regression from 9993c969, which moved Copilot endpoint resolution into per-request auth, but summarization only forwards the token and loses the resolved Enterprise URL.

    Here is a fix: main...Marvae:fix/copilot-summarization-base-url

    I tried keeping the resolved URL on the summarization model, and /compact works locally with my Copilot Enterprise account.

  6. crierr commented on Jul 20, 2026

    @crierr

    It seems this issue is also causing problems with the extensions. I tried to fix it by submitting a PR to the extension side, but I guess I'll have to wait for it to be fixed in Pi.

  7. removed
    untriagedThis issue has been auto closed and has not been triaged
    on Jul 20, 2026
  8. added a commit that references this issue on Jul 20, 2026
    434be40
  9. guru-balamurugan commented on Jul 22, 2026

    @guru-balamurugan

    Facing the same issue, using anthropic model to compact the session as a workaround and switching back to GPT models.

  10. Ali-Al1 commented on Jul 22, 2026

    @Ali-Al1

    Confirmed independently on pi 0.81.1 / pi-ai 0.81.1 with GitHub Copilot Enterprise.

    The failure is not transient and retries do not help. The resolved OAuth endpoint is being lost specifically on the summarization path:

    • active model: baseUrl = https://api.individual.githubcopilot.com
    • ModelRuntime.getAuth(model).auth.baseUrl: https://api.enterprise.githubcopilot.com
    • normal requests succeed because ModelRuntime.prepareRequest() applies the resolved auth base URL
    • AgentSession._getSummarizationRequestAuth() forwards only apiKey, headers, and env
    • compaction then sends the Enterprise token to the Individual endpoint, producing 421 Misdirected Request

    I reproduced this with the exact same large compaction input:

    1. Original model URL + explicitly forwarded auth: 421 Misdirected Request
    2. Same model/input/token, but model cloned with baseUrl: authResult.auth.baseUrl: success (139,362 tokens processed; 12,004-character summary)

    The resolved baseUrl needs to be preserved for manual compaction, auto-compaction, and branch summarization. No 421 retry classification is needed.

  11. amlucas0xff commented on Jul 22, 2026

    @amlucas0xff

    Dug into this a bit more, checking both the published @earendil-works/pi-coding-agent 0.81.1 and the current repo source, and I think there's a separate retry-classification bug here on top of whatever's going on with the Enterprise request itself.

    This was originally reported on 0.80.10, where compaction summarization didn't go through the shared retry wrapper at all yet - so on that version a 421 (or basically anything) would just fail on the first try. In 0.81.1 that's changed: compaction and branch summarization now go through retryAssistantCall(), and normal turn auto-retry has its own separate control flow, but all three end up classifying errors through the same isRetryableAssistantError() / RETRYABLE_PROVIDER_ERROR_PATTERN. That pattern covers 429/500/502/503/504/524 plus a handful of provider/transport-error strings, but it doesn't include 421 or "Misdirected Request" anywhere. So a 421 Misdirected Request gets treated as a terminal error and surfaces immediately even with retry enabled - it never actually gets a second attempt.

    RFC 9110 §15.5.20 explicitly says a client is allowed to retry a 421, even for non-idempotent requests, ideally over a different connection. So a bounded retry is a reasonable mitigation for the cases where this is just intermittent. It's not a full fix though - retryAssistantCall() doesn't force a fresh connection, and retrying obviously won't help if the request is deterministically wrong.

    One data point on that: I'm on the individual Copilot plan (api.individual.githubcopilot.com), not Enterprise, and hit the exact same 421 Misdirected Request text. So this isn't Enterprise-exclusive, but that doesn't mean my case and OP's repeatable Enterprise failure share a cause - I'd treat these as two separate failure modes for now rather than assume one explanation covers both: mine looks like it could be an intermittent thing that a retry would paper over, OP's sounds like it's failing every time, which points to something deterministic in the request/endpoint rather than flakiness.

    On the OpenClaw issue linked above - it's a good lead but I don't think it transfers directly to pi. That report is specifically about OpenClaw's own Copilot model builder omitting the IDE headers, and it even says explicitly that pi-ai's model data already had them. Checking pi's own Copilot model definitions (both 0.80.10 and 0.81.1), User-Agent, Editor-Version, Editor-Plugin-Version, and Copilot-Integration-Id are all already there, and the OAuth flow derives the base URL from the token's proxy-ep. So the Enterprise 421 here is probably a different gap - worth comparing the actual resolved base URL/headers on a failing Enterprise compaction request against a working one to see what's different.

    Two things worth landing here:

    • Add 421 to the retryable-error classifier so at least the intermittent cases self-heal instead of blowing up compaction. Long term a structured status code would be cleaner than string matching, but if it stays text-based, better to match on word boundaries and the specific "Misdirected Request" phrase rather than bare "421"/"misdirected" substrings, to avoid false positives.
    • Separately dig into what's actually different about the Enterprise request/response, since retrying alone won't fix a deterministic 421.

    Happy to put together a focused PR for the retry-classifier part against current main, with tests covering the 421 case and making sure it doesn't regress the existing non-retryable quota/billing classifications, if that'd be useful.

  12. yinshangwei commented on Jul 23, 2026

    @yinshangwei

    Same issue + 1

  13. 6 remaining items

  14. yinshangwei commented on Jul 28, 2026

    @yinshangwei

    Hi, I had the same issue and found a quick workaround, hope it helps.

    Add this to your ~/.pi/agent/models.json:

    {
    "providers": {
    "github-copilot": {
    "baseUrl": "https://api..githubcopilot.com"
    }
    }
    }
    GitHub Copilot has 3 possible plans, each with its own domain:

    • Individual/Pro → api.individual.githubcopilot.com
    • Business → api.business.githubcopilot.com
    • Enterprise → api.enterprise.githubcopilot.com

    To check which one you need:

    Open:

    ~/.pi/agent/auth.json
    

    Find your token, and look for:

    proxy-ep=proxy.XXX.githubcopilot.com
    

    Use that same XXX in the URL above (just replace proxy with api).

    After adding this:

    • Normal chat still works
    • /compact works too

    Tested on:

    pi 0.82.1
    

    Thanks, works for me!

  15. evillase commented on Aug 3, 2026

    @evillase

    Same root cause, different symptom — reporting the Copilot Business variant, since it doesn't produce the 421 this thread documents and so doesn't turn up in a search.

    On pi 0.83.0 with sku=copilot_for_business_seat_quota, proxy-ep=proxy.business.githubcopilot.com, every compaction fails as:

    Error: Connection error.
    Compaction failed: Turn prefix summarization failed: Connection error.
    

    The underlying cause, recovered by wrapping globalThis.fetch and logging error.cause, is:

    TypeError: fetch failed
      cause: RequestContentLengthMismatchError (UND_ERR_REQ_CONTENT_LENGTH_MISMATCH)
      url: https://api.individual.githubcopilot.com/v1/messages
    

    The request is aborted mid-upload rather than answered with a status, so pi flattens it to a bare "Connection error." with no hint that the host is wrong. Retries never help. I can't say whether the abort is the endpoint's behavior for business tokens or my corporate proxy swallowing the 421; I have HTTPS_PROXY set and didn't isolate it.

    Confirming @Ali-Al1's analysis holds for business seats. Measured directly with pi's bundled undici and the same dispatcher, POSTing 1 KB / 50 KB / 200 KB / 500 KB bodies:

    host result
    api.individual.githubcopilot.com content-length mismatch at every size, ~520 ms
    api.business.githubcopilot.com 200 (500 KB → 9.4 s)

    Body size is not the trigger, which is why this reproduces at low context usage — mine failed at 7.4%.

    Proposed workaround until the fix lands. Extension that rewrites the host on outbound requests to match the stored token's proxy-ep. Save as ~/.pi/agent/extensions/copilot-baseurl-fix.ts and /reload. Works for enterprise and business alike, since the host comes from the token:

    import { readFileSync } from "node:fs";
    import { homedir } from "node:os";
    import { join } from "node:path";
    import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
    
    const DEFAULT_HOST = "api.individual.githubcopilot.com";
    const FLAG = "__piCopilotBaseUrlFix";
    
    function seatHost(): string | null {
    	try {
    		const auth = JSON.parse(readFileSync(join(homedir(), ".pi", "agent", "auth.json"), "utf8"));
    		const token = auth?.["github-copilot"]?.access;
    		const proxyEp = typeof token === "string" ? /proxy-ep=([^;]+)/.exec(token)?.[1] : undefined;
    		const host = proxyEp?.replace(/^proxy\./, "api.");
    		return host && host !== DEFAULT_HOST ? host : null;
    	} catch {
    		return null;
    	}
    }
    
    function rewrite(url: string): string {
    	if (!url.includes(DEFAULT_HOST)) return url;
    	const host = seatHost();
    	return host ? url.replace(DEFAULT_HOST, host) : url;
    }
    
    export default function copilotBaseUrlFix(_pi: ExtensionAPI): void {
    	const scope = globalThis as typeof globalThis & Record<string, unknown>;
    	if (scope[FLAG]) return;
    	scope[FLAG] = true;
    
    	const originalFetch = globalThis.fetch;
    	globalThis.fetch = function patchedFetch(input, init) {
    		if (typeof input === "string") return originalFetch(rewrite(input), init);
    		if (input instanceof URL) return originalFetch(new URL(rewrite(input.href)), init);
    		const request = input as Request;
    		const url = rewrite(request.url);
    		return originalFetch(url === request.url ? request : new Request(url, request), init);
    	} as typeof globalThis.fetch;
    }

    It patches globalThis.fetch to swap the default individual host for the one in auth.json, re-reading the file per rewritten request so a token refresh is picked up.

  16. vegarsti commented on Aug 4, 2026

    @vegarsti
    Member

    Fixed by preserving the credential-resolved model endpoint through manual compaction, auto-compaction, and branch summarization. Added a regression test covering the SDK-style stream path where forwarding the resolved token previously caused the Business/Enterprise endpoint to be lost.

    This comment is AI-generated by /wr

  17. vegarsti commented on Aug 4, 2026

    @vegarsti
    Member

    Thanks for reporting!

  18. added a commit that references this issue on Aug 4, 2026
    18d65de
  19. added a commit that references this issue on Aug 4, 2026
    ab5f8d8
  20. added a commit that references this issue on Aug 11, 2026
  21. added 2 commits that reference this issue on Sep 11, 2026
    452c9c2
    ad0e869
  22. added 2 commits that reference this issue on Oct 8, 2026
    a2b2023
    422b292
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions