Devolutions/UniGetUI · warning · InvalidOperationException

GitHub did not return an access token.

Error message

GitHub did not return an access token.

What it means

In CompleteGitHubDeviceFlowAsync, after calling CreateAccessTokenForDeviceFlowAsync the returned GitHubOAuthToken's AccessToken is checked. If it is null or whitespace, an InvalidOperationException is thrown before any token is stored. This guards against GitHub returning a 200 with an empty or missing access_token field — which happens when the device authorization has not been completed by the user yet, or when the response shape is unexpected.

Source

Thrown at src/UniGetUI.Interface.IpcApi/IpcBackupApi.cs:227

        {
            ClearPendingGitHubDeviceFlow();
            throw new InvalidOperationException(
                "The pending GitHub device flow has expired. Start sign-in again."
            );
        }

        try
        {
            using var client = CreateAnonymousGitHubClient();
            var token = await client.CreateAccessTokenForDeviceFlowAsync(
                Secrets.GetGitHubClientId(),
                pending.DeviceFlow,
                CancellationToken.None
            );

            if (string.IsNullOrWhiteSpace(token.AccessToken))
            {
                throw new InvalidOperationException("GitHub did not return an access token.");
            }

            SecureGHTokenManager.StoreToken(token.AccessToken);
            using var userClient = CreateAuthenticatedGitHubClient(token.AccessToken);
            var user = await userClient.GetCurrentUserAsync();
            Settings.SetValue(Settings.K.GitHubUserLogin, user.Login ?? string.Empty);
            ClearPendingGitHubDeviceFlow();

            return new IpcGitHubAuthResult
            {
                Status = "success",
                Command = "complete-github-sign-in",
                Message = string.IsNullOrWhiteSpace(user.Login)
                    ? "GitHub sign-in completed."
                    : $"GitHub sign-in completed for {user.Login}.",
                Auth = await GetGitHubAuthInfoAsync(),
            };
        }

View on GitHub (pinned to 9b1d7d0eab)

Solutions

  1. Ensure the user has completed the browser authorization before calling CompleteGitHubDeviceFlowAsync.
  2. Poll at the device-flow interval and only complete once authorization is confirmed.
  3. If this persists, restart the device flow (StartGitHubDeviceFlowAsync) and have the user re-authorize.

Example fix

// before: completing before the user finishes authorization
await IpcBackupApi.CompleteGitHubDeviceFlowAsync();
// after: poll until the user has authorized, then complete
var auth = await IpcBackupApi.GetStatusAsync();
if (auth.Auth.IsAuthenticated || /* user confirmed browser approval */ true)
    await IpcBackupApi.CompleteGitHubDeviceFlowAsync();
Defensive patterns

Strategy: retry

Try / catch

try { await IpcBackupApi.CompleteGitHubDeviceFlowAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("did not return an access token"))
{ /* user has not finished browser auth yet; poll again or restart flow */ }

Prevention

When it happens

Trigger: The user has not yet entered the device code in the browser, or has not approved the authorization, but the token endpoint was polled and returned a body without an access_token (e.g. an 'authorization_pending' error serialized as 200, or a malformed response).

Common situations: The client polls the token endpoint before the user finishes browser authorization. GitHub returns an error descriptor (e.g. {"error":"authorization_pending"}) that deserializes into a token with an empty AccessToken. A network proxy returns an empty 200. The device code was already used or expired and GitHub returns a non-token body.

Related errors


AI-assisted analysis of Devolutions/UniGetUI@9b1d7d0eab (2026-08-13). Data as JSON: /api/errors/95c49ee9069ef62c. Report an issue: GitHub.