Devolutions/UniGetUI · warning · InvalidOperationException

No GitHub device flow is pending. Start sign-in first.

Error message

No GitHub device flow is pending. Start sign-in first.

What it means

GetPendingGitHubDeviceFlow (called by CompleteGitHubDeviceFlowAsync) reads the static _pendingGitHubDeviceFlow field under a lock. If it is null — no device flow was ever started, or the previous one was cleared by expiry/sign-out — it throws InvalidOperationException. This is a precondition check: the completion step requires a prior initiation step.

Source

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

        return PackageBackupStartingKey + " " + deviceUser;
    }

    private static string ValidateBackupKey(string key)
    {
        if (string.IsNullOrWhiteSpace(key))
        {
            throw new InvalidOperationException("The backup key is required.");
        }

        return key;
    }

    private static PendingGitHubDeviceFlow GetPendingGitHubDeviceFlow()
    {
        lock (GitHubAuthLock)
        {
            return _pendingGitHubDeviceFlow
                ?? throw new InvalidOperationException(
                    "No GitHub device flow is pending. Start sign-in first."
                );
        }
    }

    private static void ClearPendingGitHubDeviceFlow()
    {
        lock (GitHubAuthLock)
        {
            _pendingGitHubDeviceFlow = null;
        }
    }
}

View on GitHub (pinned to 9b1d7d0eab)

Solutions

  1. Always call StartGitHubDeviceFlowAsync before CompleteGitHubDeviceFlowAsync.
  2. Check IpcGitHubAuthInfo.DeviceFlowPending (via GetStatusAsync) before attempting completion.
  3. If the flow was cleared by expiry or restart, start a new one.
  4. Ensure start and complete happen in the same process/session, since the pending flow is in-memory only.

Example fix

// before: completing without starting
await IpcBackupApi.CompleteGitHubDeviceFlowAsync();
// after: start first, then complete
var status = await IpcBackupApi.GetStatusAsync();
if (!status.Auth.DeviceFlowPending)
    await IpcBackupApi.StartGitHubDeviceFlowAsync(new IpcGitHubDeviceFlowRequest { LaunchBrowser = true });
await IpcBackupApi.CompleteGitHubDeviceFlowAsync();
Defensive patterns

Strategy: validation

Validate before calling

var status = await IpcBackupApi.GetStatusAsync();
if (!status.Auth.DeviceFlowPending)
    await IpcBackupApi.StartGitHubDeviceFlowAsync(new IpcGitHubDeviceFlowRequest { LaunchBrowser = true });

Type guard

static bool HasPendingDeviceFlow(IpcGitHubAuthInfo auth) => auth.DeviceFlowPending;

Try / catch

try { await IpcBackupApi.CompleteGitHubDeviceFlowAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("No GitHub device flow is pending"))
{ await IpcBackupApi.StartGitHubDeviceFlowAsync(new IpcGitHubDeviceFlowRequest { LaunchBrowser = true }); }

Prevention

When it happens

Trigger: CompleteGitHubDeviceFlowAsync is called without a preceding successful StartGitHubDeviceFlowAsync. Or the pending flow was already cleared by: expiry (CompleteGitHubDeviceFlowAsync itself clears it on timeout), sign-out (SignOutGitHubAsync), or GetGitHubAuthInfoAsync's auto-clear on expiry. In a multi-daemon scenario, the pending flow lives in a different process's static field.

Common situations: The client calls complete before start. The flow expired and was auto-cleared by a status check. The user signed out between start and complete. A second process/session is involved (the static field is per-process). The app restarted, losing the in-memory pending flow.

Related errors


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