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.

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.

Appendix: 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)