openclaw - ✅(Solved) Fix GitHub Copilot provider: missing Editor-Version header on inference requests causes HTTP 400 [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#58056Fetched 2026-04-08 01:54:27
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
7
Author
Participants
Timeline (top)
subscribed ×2commented ×1cross-referenced ×1referenced ×1

Error Message

  1. Observe in logs: "error":"HTTP 400: bad request: missing Editor-Version header for IDE auth"

Root Cause

The Editor-Version header is correctly set in the auth/usage code paths:

  • usage-DLRV_xyV.js"Editor-Version": "vscode/1.96.2" (usage endpoint)
  • auth-profiles-B5ypC5S-.js"Editor-Version": "vscode/1.96.2" (token exchange)

However, the inference request path (the actual model API call via prepareRuntimeAuth in extensions/github-copilot/index.ts) only returns { apiKey, baseUrl, expiresAt } — there is no mechanism to inject custom headers into the outgoing model request.

The ProviderPreparedRuntimeAuth type only supports:

type ProviderPreparedRuntimeAuth = {
    apiKey: string;
    baseUrl?: string;
    expiresAt?: number;
};

There is no headers or extraHeaders field, so even though the GitHub Copilot API requires Editor-Version, Editor-Plugin-Version, and Copilot-Integration-Id headers, the plugin has no way to provide them through this hook.

Fix Action

Workaround

Adding headers via models.providers.github-copilot.headers in config partially works but requires a full provider entry with baseUrl and models[], which conflicts with the plugin's dynamic prepareRuntimeAuth base URL resolution. The config schema supports headers on provider entries, but the plugin-owned provider may not merge them.

PR fix notes

PR #62257: fix(github-copilot): return IDE identity headers from prepareRuntimeAuth

Description (problem / solution / changelog)

Summary

Fixes #58056 — GitHub Copilot provider returns HTTP 400 missing Editor-Version header for IDE auth on all inference requests.

The prior fix in #60755 added IDE identity headers (Editor-Version, User-Agent) to openclaw's transport layer (copilot-dynamic-headers.ts) and the extension's wrapStreamFn. However, these code paths are bypassed during normal inference because:

  1. Boundary-aware transport is never activated. pi-coding-agent wraps streamSimple in a custom auth-injecting function during createAgentSession. The identity check in stream-resolution.ts:116 (currentStreamFn === streamSimple) fails because it's a different function reference, so openclaw's transport layer — which has the correct headers — is never used.

  2. The actual inference path falls through to @mariozechner/pi-ai's internal providers (anthropic.js, openai-responses.js, openai-completions.js), which call pi-ai's own buildCopilotDynamicHeaders from dist/providers/github-copilot-headers.js. This is a separate copy that was never updated with the IDE headers — it only returns X-Initiator, Openai-Intent, and Copilot-Vision-Request.

  3. The extension's wrapStreamFn only covers anthropic-messages API (stream.ts:16), leaving GPT-through-Copilot models (openai-responses API) with no IDE header injection at all.

Fix

Return the IDE headers from prepareRuntimeAuth via the existing ProviderPreparedRuntimeAuth.request.headers contract. This is a one-line addition:

request: { headers: buildCopilotIdeHeaders() },

These headers flow through applyPreparedRuntimeRequestOverridesresolveProviderRequestConfigmodel.headers. Pi-ai's providers read model.headers early in their mergeHeaders chain, so the IDE headers reach the outgoing HTTP request regardless of which stream function is active.

This fixes:

  • anthropic-messages API (Claude models through Copilot)
  • openai-responses API (GPT models through Copilot)
  • Both the main inference path and the compaction path

Also exports buildCopilotIdeHeaders from openclaw/plugin-sdk/provider-stream-shared so the extension can use it without violating boundary rules.

Remaining issues (not addressed here)

  • stream-resolution.ts:116 identity check: The === streamSimple check should be relaxed to also activate the boundary-aware transport for pi-coding-agent sessions. This would let openclaw's transport layer handle Copilot requests directly, bypassing pi-ai's providers entirely.
  • @mariozechner/pi-ai stale headers: Pi-ai's buildCopilotDynamicHeaders should eventually be updated to include IDE headers for defense in depth.
  • Simple completion path: Label generation and TTS use streamSimple without the prepareRuntimeAuth flow, so they remain affected. Lower severity since these are auxiliary tasks.

Test plan

  • Verify openclaw gateway run with github-copilot/claude-sonnet-4.6 no longer returns HTTP 400
  • Verify GPT models through Copilot (github-copilot/gpt-4o) also work
  • Existing extensions/github-copilot/stream.test.ts continues to pass
  • auth-controller.test.ts tests for applyPreparedRuntimeRequestOverrides pass with request.headers

🤖 Generated with Claude Code

Changed files

  • extensions/github-copilot/index.ts (modified, +2/-0)
  • src/plugin-sdk/provider-stream-shared.ts (modified, +1/-0)

PR #34: feat: add GitHub Copilot as a model provider

Description (problem / solution / changelog)

Summary

  • Add GitHub Copilot as a new model provider, integrating the Copilot API (api.githubcopilot.com) as an OpenAI-compatible backend. Supports chat completions, dynamic model listing via /models endpoint, and provider switching via CLAUDE_CODE_USE_COPILOT=1 or /provider copilot.
  • Register OpenAI env vars (OPENAI_API_KEY, OPENAI_BASE_URL, etc.) in managed and safe env var sets, add refreshModelStringsForCurrentProvider utility, and include a test for the OpenAI-compatible fetch adapter.
  • Rename package from claude-code-source-snapshot to free-code.

Usage

CLAUDE_CODE_USE_COPILOT=1 COPILOT_TOKEN=<github_oauth_token> ./cli

Or interactively: /provider copilot

Files Changed

  • src/utils/model/providers.ts — Added copilot to APIProvider type
  • src/utils/model/configs.ts — Added copilot field to all model configs
  • src/services/api/client.ts — Added Copilot client branch with Copilot-specific headers
  • src/services/api/bootstrap.ts — Added Copilot model refresh on startup
  • src/services/api/openaiCompatible.ts — New: OpenAI-compatible fetch adapter + refreshCopilotModelOptions
  • src/commands/provider/ — New: provider switching command with Copilot support
  • src/utils/model/model.ts — Added Copilot to default model selection
  • src/utils/model/modelOptions.ts — Added Copilot to model options
  • src/utils/status.tsx — Show Copilot base URL in /status
  • src/utils/managedEnvConstants.ts — Register OpenAI env vars
  • src/utils/model/modelStrings.ts — Add provider model refresh utility
  • package.json — Rename to free-code
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit

  • New Features

    • Added /provider command to switch between API providers: first-party Anthropic, Bedrock, Vertex AI, Foundry, OpenAI-compatible, and GitHub Copilot
    • Extended model catalog with Copilot-compatible model identifiers
    • Added support for OpenAI-compatible API endpoints
  • Package Changes

    • Updated package name to "free-code"
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Changed files

  • package.json (modified, +1/-1)
  • src/commands.ts (modified, +2/-0)
  • src/commands/provider/index.ts (added, +13/-0)
  • src/commands/provider/provider.ts (added, +180/-0)
  • src/services/api/bootstrap.ts (modified, +70/-56)
  • src/services/api/client.ts (modified, +170/-120)
  • src/services/api/openaiCompatible.test.ts (added, +161/-0)
  • src/services/api/openaiCompatible.ts (added, +657/-0)
  • src/utils/managedEnvConstants.ts (modified, +12/-0)
  • src/utils/model/configs.ts (modified, +108/-95)
  • src/utils/model/model.ts (modified, +255/-243)
  • src/utils/model/modelOptions.ts (modified, +239/-223)
  • src/utils/model/modelStrings.ts (modified, +17/-0)
  • src/utils/model/providers.ts (modified, +26/-18)
  • src/utils/status.tsx (modified, +248/-165)

Code Example

HTTP 400: bad request: missing Editor-Version header for IDE auth

---

type ProviderPreparedRuntimeAuth = {
    apiKey: string;
    baseUrl?: string;
    expiresAt?: number;
};

---

"github-copilot": {
  "claude-haiku-4.5": {
    headers: {
      "User-Agent": "GitHubCopilotChat/0.35.0",
      "Editor-Version": "vscode/1.107.0",
      "Editor-Plugin-Version": "copilot-chat/0.35.0",
      "Copilot-Integration-Id": "vscode-chat"
    }
  }
}

---

type ProviderPreparedRuntimeAuth = {
    apiKey: string;
    baseUrl?: string;
    expiresAt?: number;
    headers?: Record<string, string>;  // <-- new
};

---

prepareRuntimeAuth: async (ctx) => {
    const token = await resolveCopilotApiToken({
        githubToken: ctx.apiKey,
        env: ctx.env
    });
    return {
        apiKey: token.token,
        baseUrl: token.baseUrl,
        expiresAt: token.expiresAt,
        headers: {
            "Editor-Version": "vscode/1.107.0",
            "Editor-Plugin-Version": "copilot-chat/0.35.0",
            "Copilot-Integration-Id": "vscode-chat",
            "User-Agent": "GitHubCopilotChat/0.35.0"
        }
    };
},
RAW_BUFFERClick to expand / collapse

Bug Description

The GitHub Copilot provider plugin does not send the required Editor-Version header on model inference API requests, resulting in:

HTTP 400: bad request: missing Editor-Version header for IDE auth

Root Cause

The Editor-Version header is correctly set in the auth/usage code paths:

  • usage-DLRV_xyV.js"Editor-Version": "vscode/1.96.2" (usage endpoint)
  • auth-profiles-B5ypC5S-.js"Editor-Version": "vscode/1.96.2" (token exchange)

However, the inference request path (the actual model API call via prepareRuntimeAuth in extensions/github-copilot/index.ts) only returns { apiKey, baseUrl, expiresAt } — there is no mechanism to inject custom headers into the outgoing model request.

The ProviderPreparedRuntimeAuth type only supports:

type ProviderPreparedRuntimeAuth = {
    apiKey: string;
    baseUrl?: string;
    expiresAt?: number;
};

There is no headers or extraHeaders field, so even though the GitHub Copilot API requires Editor-Version, Editor-Plugin-Version, and Copilot-Integration-Id headers, the plugin has no way to provide them through this hook.

Evidence

The built-in model catalog (control-ui/assets/event-stream-*.js) does define the correct headers per-model:

"github-copilot": {
  "claude-haiku-4.5": {
    headers: {
      "User-Agent": "GitHubCopilotChat/0.35.0",
      "Editor-Version": "vscode/1.107.0",
      "Editor-Plugin-Version": "copilot-chat/0.35.0",
      "Copilot-Integration-Id": "vscode-chat"
    }
  }
}

But these catalog-defined headers are not applied to the actual inference fetch call, only to catalog metadata.

Reproduction

  1. Install OpenClaw v2026.3.28 on a headless server (e.g., AWS Lightsail)
  2. Run openclaw models auth login-github-copilot
  3. Set model: openclaw models set github-copilot/claude-sonnet-4.6
  4. Send a message via any channel (Telegram, web UI, etc.)
  5. Observe in logs: "error":"HTTP 400: bad request: missing Editor-Version header for IDE auth"

Workaround

Adding headers via models.providers.github-copilot.headers in config partially works but requires a full provider entry with baseUrl and models[], which conflicts with the plugin's dynamic prepareRuntimeAuth base URL resolution. The config schema supports headers on provider entries, but the plugin-owned provider may not merge them.

Suggested Fix

Option A (minimal): Extend ProviderPreparedRuntimeAuth to support an optional headers field:

type ProviderPreparedRuntimeAuth = {
    apiKey: string;
    baseUrl?: string;
    expiresAt?: number;
    headers?: Record<string, string>;  // <-- new
};

Then in extensions/github-copilot/index.ts, update prepareRuntimeAuth:

prepareRuntimeAuth: async (ctx) => {
    const token = await resolveCopilotApiToken({
        githubToken: ctx.apiKey,
        env: ctx.env
    });
    return {
        apiKey: token.token,
        baseUrl: token.baseUrl,
        expiresAt: token.expiresAt,
        headers: {
            "Editor-Version": "vscode/1.107.0",
            "Editor-Plugin-Version": "copilot-chat/0.35.0",
            "Copilot-Integration-Id": "vscode-chat",
            "User-Agent": "GitHubCopilotChat/0.35.0"
        }
    };
},

And update the inference fetch layer to merge headers from ProviderPreparedRuntimeAuth into the outgoing request.

Option B: Have the inference layer read per-model headers from the built-in catalog (which already defines them correctly) and apply them to the fetch call.

Environment

  • OpenClaw: v2026.3.28
  • OS: Ubuntu (AWS Lightsail)
  • Node: v22.22.2
  • Channel: Telegram + Web UI
  • Model: github-copilot/claude-sonnet-4.6 (also affects all other Copilot models)

extent analysis

Fix Plan

To resolve the issue, we will implement Option A (minimal): extend ProviderPreparedRuntimeAuth to support an optional headers field and update the prepareRuntimeAuth function to include the required headers.

Step 1: Update ProviderPreparedRuntimeAuth type

type ProviderPreparedRuntimeAuth = {
    apiKey: string;
    baseUrl?: string;
    expiresAt?: number;
    headers?: Record<string, string>;
};

Step 2: Update prepareRuntimeAuth function

prepareRuntimeAuth: async (ctx) => {
    const token = await resolveCopilotApiToken({
        githubToken: ctx.apiKey,
        env: ctx.env
    });
    return {
        apiKey: token.token,
        baseUrl: token.baseUrl,
        expiresAt: token.expiresAt,
        headers: {
            "Editor-Version": "vscode/1.107.0",
            "Editor-Plugin-Version": "copilot-chat/0.35.0",
            "Copilot-Integration-Id": "vscode-chat",
            "User-Agent": "GitHubCopilotChat/0.35.0"
        }
    };
},

Step 3: Update inference fetch layer to merge headers

// Assuming the fetch function is named 'fetchInference'
fetchInference: async (ctx, auth) => {
    const headers = auth.headers || {};
    // Merge headers from auth into the outgoing request
    const response = await fetch(ctx.baseUrl, {
        method: 'POST',
        headers: {
            ...headers,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(ctx.requestBody)
    });
    return response.json();
},

Verification

To verify the fix, follow these steps:

  1. Update the code with the changes mentioned above.
  2. Restart the OpenClaw server.
  3. Run openclaw models auth login-github-copilot and set the model to github-copilot/claude-sonnet-4.6.
  4. Send a message via any channel (Telegram, web UI, etc.).
  5. Check the logs for any errors related to missing headers.

Extra Tips

  • Make sure to handle any potential errors that may occur during the fetch request.
  • Consider adding logging to track the headers being sent with each request.
  • If using a version control system, create a new branch for the changes and merge them into the main branch after verification.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix GitHub Copilot provider: missing Editor-Version header on inference requests causes HTTP 400 [2 pull requests, 1 comments, 2 participants]