{"record":{"id":"a508b39d715217b0","repo":"github/copilot-sdk","slug":"github-token-provider-returned-neither-a-token-nor","errorCode":null,"errorMessage":"GitHub token provider returned neither a token nor cancellation.","messagePattern":"GitHub token provider returned neither a token nor cancellation\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"dotnet/src/Client.cs","lineNumber":2119,"sourceCode":"            var reason = request.Reason == GitHubTokenAcquireReason.Initial\n                ? GitHubTokenRequestReason.Initial\n                : request.Reason == GitHubTokenAcquireReason.Refresh\n                    ? GitHubTokenRequestReason.Refresh\n                    : throw new InvalidOperationException($\"Unknown GitHub token request reason '{request.Reason}'.\");\n            var result = await provider(new GitHubTokenProviderArgs\n            {\n                Host = request.Host,\n                SessionId = request.SessionId,\n                Reason = reason,\n            }).ConfigureAwait(false);\n\n            if (result is { Cancelled: true })\n            {\n                return new GitHubTokenAcquireResultCancelled();\n            }\n            if (result?.Token is not { } token)\n            {\n                throw new InvalidOperationException(\n                    \"GitHub token provider returned neither a token nor cancellation.\");\n            }\n            return new GitHubTokenAcquireResultToken\n            {\n                AccessToken = token.AccessToken,\n                TokenType = token.TokenType,\n                ExpiresIn = token.ExpiresIn,\n            };\n        }\n    }\n\n    /// <summary>\n    /// Tells the runtime to route its outbound model-layer requests through this\n    /// client's LLM inference provider. No-op when interception is not configured.\n    /// </summary>\n    private async Task ConfigureLlmInferenceAsync(CancellationToken cancellationToken)\n    {\n        if (_clientGlobalApis?.LlmInference is null)","sourceCodeStart":2101,"sourceCodeEnd":2137,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/dotnet/src/Client.cs#L2101-L2137","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nreturn new GitHubTokenAcquireResult(); // empty, no token, not cancelled\n\n// after\nvar token = await AcquireTokenAsync();\nreturn token is null\n    ? new GitHubTokenAcquireResultCancelled()\n    : new GitHubTokenAcquireResultToken { AccessToken = token, TokenType = \"bearer\" };","handlingStrategy":"validation","validationCode":"var result = provider.Acquire();\nif (result is not { Cancelled: true } && result?.Token is null)\n    throw new InvalidOperationException(\"Token provider must return a token or cancel\");","typeGuard":"static bool HasUsableToken(GitHubTokenAcquireResult? r) => r is { Cancelled: false, Token.AccessToken: { Length: > 0 } };","tryCatchPattern":"try\n{\n    await client.StartAsync();\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"neither a token nor cancellation\"))\n{\n    logger.LogError(ex, \"GitHub token provider misconfigured\");\n}","preventionTips":["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."],"tags":["authentication","token-provider","invalid-state"],"backgroundTag":"missing-credentials","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}