github/copilot-sdk · error · InvalidOperationException
GitHub token provider returned neither a token nor…
Error message
GitHub token provider returned neither a token nor cancellation.
What it means
CopilotClient calls a user-supplied GitHub token provider (the `tokenProvider` callback) to acquire an access token. The provider must return either a token or a cancelled result. When it returns a result object that is neither cancelled nor carries a token, the client cannot proceed and throws this InvalidOperationException.
Solutions
- Fix the token provider so it returns a populated result with a non-null Token on success.
- When authentication cannot proceed, return a cancelled result (GitHubTokenAcquireResultCancelled) or null instead of an empty result object.
- Add logging inside the provider to verify why Token was null (expired credential, missing scope, network failure).
- Wrap the provider's token source (e.g. `gh auth token`, device flow) in validation before returning.
Example fix
// before
return new GitHubTokenAcquireResult(); // empty, no token, not cancelled
// after
var token = await AcquireTokenAsync();
return token is null
? new GitHubTokenAcquireResultCancelled()
: new GitHubTokenAcquireResultToken { AccessToken = token, TokenType = "bearer" }; Defensive patterns
Strategy: validation
Validate before calling
var result = provider.Acquire();
if (result is not { Cancelled: true } && result?.Token is null)
throw new InvalidOperationException("Token provider must return a token or cancel"); Type guard
static bool HasUsableToken(GitHubTokenAcquireResult? r) => r is { Cancelled: false, Token.AccessToken: { Length: > 0 } }; Try / catch
try
{
await client.StartAsync();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("neither a token nor cancellation"))
{
logger.LogError(ex, "GitHub token provider misconfigured");
} Prevention
- Always return GitHubTokenAcquireResultCancelled (or null) when acquisition fails instead of an empty result.
- Unit-test the provider for the empty-result edge case.
- Log the provider's internal failure reason before returning.
When it happens
Trigger: The registered GitHub token provider callback returns a result object (e.g. a GitHubTokenAcquireResult with a null/missing Token and Cancelled=false), so `result?.Token is not { } token` matches and the throw at Client.cs:2119 fires.
Common situations: A custom token provider that returns a default-constructed/empty result after a failed internal lookup; a provider that logs an error but still returns a non-null, non-cancelled result instead of returning null or cancelling; providers built from partially parsed responses where AccessToken was never populated.
Related errors
- GitHubToken and GitHubTokenProvider cannot be used together.
- GitHubToken and GitHubTokenProvider cannot be used together
- GitHubToken and UseLoggedInUser cannot be used with…
- GitHubToken and UseLoggedInUser cannot be used with CliUrl…
- Invalid entry '*': there is no bare wildcard. Use one or…
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/a508b39d715217b0.
Report an issue: GitHub.
Appendix: source
Thrown at dotnet/src/Client.cs:2119
var reason = request.Reason == GitHubTokenAcquireReason.Initial
? GitHubTokenRequestReason.Initial
: request.Reason == GitHubTokenAcquireReason.Refresh
? GitHubTokenRequestReason.Refresh
: throw new InvalidOperationException($"Unknown GitHub token request reason '{request.Reason}'.");
var result = await provider(new GitHubTokenProviderArgs
{
Host = request.Host,
SessionId = request.SessionId,
Reason = reason,
}).ConfigureAwait(false);
if (result is { Cancelled: true })
{
return new GitHubTokenAcquireResultCancelled();
}
if (result?.Token is not { } token)
{
throw new InvalidOperationException(
"GitHub token provider returned neither a token nor cancellation.");
}
return new GitHubTokenAcquireResultToken
{
AccessToken = token.AccessToken,
TokenType = token.TokenType,
ExpiresIn = token.ExpiresIn,
};
}
}
/// <summary>
/// Tells the runtime to route its outbound model-layer requests through this
/// client's LLM inference provider. No-op when interception is not configured.
/// </summary>
private async Task ConfigureLlmInferenceAsync(CancellationToken cancellationToken)
{
if (_clientGlobalApis?.LlmInference is null)View on GitHub (pinned to cd8cf15dc3)