Describe the bug
Describe the bug
On a GitHub Enterprise Cloud with Data Residency (GHEC-DR) + EMU tenant (<tenant>.ghe.com), the GitHub.Copilot.SDK (.NET) per-session authentication mechanisms — SessionConfig.GitHubToken (stable field) and SessionConfig.GitHubTokenProvider (experimental field) — fail to route Copilot validation calls to the tenant's own endpoint (api.<tenant>.ghe.com / copilot-api.<tenant>.ghe.com). Instead they fall back to the public api.github.com, which rejects the tenant's gho_ token with 401 Bad credentials.
This is the same defect class as #4527 ("copilot -p fails with 401 on GHEC data residency ... model-catalog fetch hits api.githubcopilot.com instead of the tenant endpoint"), which was fixed for the CLI's non-interactive prompt-mode path. The fix does not appear to cover the SDK's session-level credential-forwarding path used for multi-user/server integrations, which is the officially documented pattern for this exact use case (see SDK README "Advanced Usage" / GitHubTokenProvider docs).
Steps to reproduce the behavior
- Authenticate a web user against
<tenant>.ghe.com via a standard OAuth Authorization Code flow (separate from copilot login), obtaining a valid gho_ user access token scoped to the tenant.
- Create a client explicitly for the documented multi-user/server pattern:
var client = new CopilotClient(new CopilotClientOptions
{
Mode = CopilotClientMode.Empty,
BaseDirectory = "<isolated temp dir>",
UseLoggedInUser = false
// no GitHubToken set at the client level
});
await client.StartAsync();
- Create a session using either documented per-session mechanism:
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5",
GitHubToken = userToken, // stable field
AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated) // required by Mode.Empty
});
or
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5",
GitHubTokenProvider = args => Task.FromResult(GitHubTokenProviderResult.FromToken(new GitHubToken
{
AccessToken = userToken,
ExpiresIn = 8 * 60 * 60
})),
AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated)
});
- Send any prompt.
What the user sees
SessionConfig.GitHubToken: CreateSessionAsync throws with:
Communication error with Copilot CLI: Request session.create failed with message:
SDK session authentication failed: Failed to fetch Copilot user info: 401 Unauthorized: {"message":"Bad credentials"}
Confirmed (via process-level environment variable injection of GITHUB_HOST, GH_HOST, GITHUB_API_URL set to the tenant host, merged with the full parent environment rather than replacing it) that this call always targets https://api.github.com/copilot_internal/user, regardless of these variables. The token is a valid tenant token — it simply cannot be valid against api.github.com.
SessionConfig.GitHubTokenProvider: CreateSessionAsync throws with a local, pre-network validation error instead:
ErrorType=query Message=Execution failed: InvalidArg, No GitHub OAuth token or Copilot HMAC key provided
No outbound network call is observed for this path at all — the provided callback does not appear to be invoked before this error fires, regardless of CopilotClientMode.
Expected behavior
Per the SDK's own documentation, both SessionConfig.GitHubToken and SessionConfig.GitHubTokenProvider should let the runtime resolve the Copilot API base URL from the tenant associated with the supplied token (api.<tenant>.ghe.com / copilot-api.<tenant>.ghe.com), exactly as the ambient UseLoggedInUser = true path already does successfully for the same tenant/token combination.
What we ruled out
| Attempt |
Result |
UseLoggedInUser = true (ambient identity, same tenant/account) |
Works — proves token/tenant/account are valid |
GITHUB_HOST / GH_HOST / GITHUB_API_URL env vars on the client process (merged with the full parent environment, not replacing it) |
No effect — still routes to api.github.com |
CopilotClientMode.Empty with BaseDirectory and AvailableTools set (both required, undocumented prerequisites discovered via trial/error) |
Session construction succeeds, but the auth call still mis-routes |
Different token sources (OAuth Authorization Code flow token, copilot-cli token extracted from Windows Credential Manager) |
Identical failure for both |
Additional context
Same host mis-routing defect class as #76 (fixed in v0.0.332) and #4527 (fixed, but only for the CLI -p prompt-mode code path). Possibly related to #4378 (MCP registry policy fetch, same GHEC-DR fallback pattern, different subsystem). This report is scoped specifically to the GitHub.Copilot.SDK .NET session-level credential APIs (SessionConfig.GitHubToken, SessionConfig.GitHubTokenProvider), which does not appear to be covered by any of the above fixes as of CLI 1.0.84-5 (bundled with SDK 1.0.15-preview.0).
This blocks the documented multi-user/server integration pattern (one shared runtime serving many end users, each with their own GitHub Copilot entitlement) for any GHEC Data Residency tenant — the SDK's flagship use case for SessionConfig.GitHubToken/GitHubTokenProvider.
Suggested fix: apply the same per-subscription/tenant endpoint resolution used by the ambient (UseLoggedInUser) path to the SessionConfig.GitHubToken and GitHubTokenProvider code paths — likely by deriving the tenant host from the token itself rather than defaulting to api.github.com.
Affected version
GitHub.Copilot.SDK (.NET NuGet package): 1.0.15-preview.0
Bundled/pinned Copilot CLI runtime: 1.0.84-5 (older than 1.0.85, referenced as fixing #4527)
Environment
- Host tenant type: GitHub Enterprise Cloud with Data Residency + Enterprise Managed Users (confirmed via
"plan":{"name":"emu_user"} on GET https://api.<tenant>.ghe.com/user)
- Operating system: Windows 11
- CPU architecture: x86_64 (AMD64)
- .NET: 8 (SDK preview 11.0.100-rc.1.26425.128)
- Auth method: OAuth Authorization Code flow token (separate from
copilot login)
Affected version
No response
Steps to reproduce the behavior
-
On a GHEC Data Residency tenant (<tenant>.ghe.com), obtain a valid gho_ user access token via a standard OAuth Authorization Code flow (not copilot login).
-
Create a CopilotClient explicitly configured for the documented multi-user/server pattern, with no client-level GitHub credential:
var client = new CopilotClient(new CopilotClientOptions
{
Mode = CopilotClientMode.Empty,
BaseDirectory = "<isolated temp dir>",
UseLoggedInUser = false
});
await client.StartAsync();
-
Create a session passing the token via the stable SessionConfig.GitHubToken field:
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5",
GitHubToken = userToken,
AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated)
});
-
Observe CreateSessionAsync throw:
SDK session authentication failed: Failed to fetch Copilot user info: 401 Unauthorized: {"message":"Bad credentials"}
-
Repeat step 3 using SessionConfig.GitHubTokenProvider instead:
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5",
GitHubTokenProvider = args => Task.FromResult(GitHubTokenProviderResult.FromToken(new GitHubToken
{
AccessToken = userToken,
ExpiresIn = 8 * 60 * 60
})),
AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated)
});
-
Observe a different, local (pre-network) failure instead:
Execution failed: InvalidArg, No GitHub OAuth token or Copilot HMAC key provided
-
Confirm the same token/tenant/account works correctly via the ambient path, ruling out a token or tenant configuration issue:
var client = new CopilotClient(new CopilotClientOptions { UseLoggedInUser = true });
-
(Optional, to rule out host-resolution env vars) Re-run step 2–4 with GITHUB_HOST, GH_HOST, and GITHUB_API_URL set to the tenant host in CopilotClientOptions.Environment (merged with the full parent environment). Observe identical failure — the runtime still calls api.github.com for the Copilot user info check.
Expected behavior
Both SessionConfig.GitHubToken and SessionConfig.GitHubTokenProvider should let the runtime resolve the Copilot API base URL from the tenant associated with the supplied token (api.<tenant>.ghe.com / copilot-api.<tenant>.ghe.com), exactly as the ambient UseLoggedInUser = true path already does successfully for the same tenant/token combination — session creation should succeed and the prompt should return a normal assistant response, without ever contacting api.github.com.
Secondarily:
Additional context
No response
Describe the bug
Describe the bug
On a GitHub Enterprise Cloud with Data Residency (GHEC-DR) + EMU tenant (
<tenant>.ghe.com), theGitHub.Copilot.SDK(.NET) per-session authentication mechanisms —SessionConfig.GitHubToken(stable field) andSessionConfig.GitHubTokenProvider(experimental field) — fail to route Copilot validation calls to the tenant's own endpoint (api.<tenant>.ghe.com/copilot-api.<tenant>.ghe.com). Instead they fall back to the publicapi.github.com, which rejects the tenant'sgho_token with401 Bad credentials.This is the same defect class as #4527 ("
copilot -pfails with 401 on GHEC data residency ... model-catalog fetch hitsapi.githubcopilot.cominstead of the tenant endpoint"), which was fixed for the CLI's non-interactive prompt-mode path. The fix does not appear to cover the SDK's session-level credential-forwarding path used for multi-user/server integrations, which is the officially documented pattern for this exact use case (see SDK README "Advanced Usage" /GitHubTokenProviderdocs).Steps to reproduce the behavior
<tenant>.ghe.comvia a standard OAuth Authorization Code flow (separate fromcopilot login), obtaining a validgho_user access token scoped to the tenant.What the user sees
SessionConfig.GitHubToken:CreateSessionAsyncthrows with:Confirmed (via process-level environment variable injection of
GITHUB_HOST,GH_HOST,GITHUB_API_URLset to the tenant host, merged with the full parent environment rather than replacing it) that this call always targetshttps://api.github.com/copilot_internal/user, regardless of these variables. The token is a valid tenant token — it simply cannot be valid againstapi.github.com.SessionConfig.GitHubTokenProvider:CreateSessionAsyncthrows with a local, pre-network validation error instead:No outbound network call is observed for this path at all — the provided callback does not appear to be invoked before this error fires, regardless of
CopilotClientMode.Expected behavior
Per the SDK's own documentation, both
SessionConfig.GitHubTokenandSessionConfig.GitHubTokenProvidershould let the runtime resolve the Copilot API base URL from the tenant associated with the supplied token (api.<tenant>.ghe.com/copilot-api.<tenant>.ghe.com), exactly as the ambientUseLoggedInUser = truepath already does successfully for the same tenant/token combination.What we ruled out
UseLoggedInUser = true(ambient identity, same tenant/account)GITHUB_HOST/GH_HOST/GITHUB_API_URLenv vars on the client process (merged with the full parent environment, not replacing it)api.github.comCopilotClientMode.EmptywithBaseDirectoryandAvailableToolsset (both required, undocumented prerequisites discovered via trial/error)copilot-clitoken extracted from Windows Credential Manager)Additional context
Same host mis-routing defect class as #76 (fixed in v0.0.332) and #4527 (fixed, but only for the CLI
-pprompt-mode code path). Possibly related to #4378 (MCP registry policy fetch, same GHEC-DR fallback pattern, different subsystem). This report is scoped specifically to theGitHub.Copilot.SDK.NET session-level credential APIs (SessionConfig.GitHubToken,SessionConfig.GitHubTokenProvider), which does not appear to be covered by any of the above fixes as of CLI1.0.84-5(bundled with SDK1.0.15-preview.0).This blocks the documented multi-user/server integration pattern (one shared runtime serving many end users, each with their own GitHub Copilot entitlement) for any GHEC Data Residency tenant — the SDK's flagship use case for
SessionConfig.GitHubToken/GitHubTokenProvider.Suggested fix: apply the same per-subscription/tenant endpoint resolution used by the ambient (
UseLoggedInUser) path to theSessionConfig.GitHubTokenandGitHubTokenProvidercode paths — likely by deriving the tenant host from the token itself rather than defaulting toapi.github.com.Affected version
Environment
"plan":{"name":"emu_user"}onGET https://api.<tenant>.ghe.com/user)copilot login)Affected version
No response
Steps to reproduce the behavior
On a GHEC Data Residency tenant (
<tenant>.ghe.com), obtain a validgho_user access token via a standard OAuth Authorization Code flow (notcopilot login).Create a
CopilotClientexplicitly configured for the documented multi-user/server pattern, with no client-level GitHub credential:Create a session passing the token via the stable
SessionConfig.GitHubTokenfield:Observe
CreateSessionAsyncthrow:Repeat step 3 using
SessionConfig.GitHubTokenProviderinstead:Observe a different, local (pre-network) failure instead:
Confirm the same token/tenant/account works correctly via the ambient path, ruling out a token or tenant configuration issue:
(Optional, to rule out host-resolution env vars) Re-run step 2–4 with
GITHUB_HOST,GH_HOST, andGITHUB_API_URLset to the tenant host inCopilotClientOptions.Environment(merged with the full parent environment). Observe identical failure — the runtime still callsapi.github.comfor theCopilot user infocheck.Expected behavior
Both
SessionConfig.GitHubTokenandSessionConfig.GitHubTokenProvidershould let the runtime resolve the Copilot API base URL from the tenant associated with the supplied token (api.<tenant>.ghe.com/copilot-api.<tenant>.ghe.com), exactly as the ambientUseLoggedInUser = truepath already does successfully for the same tenant/token combination — session creation should succeed and the prompt should return a normal assistant response, without ever contactingapi.github.com.Secondarily:
SessionConfig.GitHubTokenProvidershould actually invoke the supplied callback (with aGitHubTokenProviderArgs.Hostreflecting the resolved tenant host) before failing, rather than failing locally with a generic "no token provided" error that never reaches the callback.copilot -pfails with 401 on GHEC data residency since 1.0.81-1 — prompt mode model-catalog fetch hitsapi.githubcopilot.cominstead of the tenant endpoint #4527), instead of a genericBad credentialsmessage that gives no indication the wrong host was contacted.Additional context
No response