git-ecosystem/git-credential-manager · error · Trace2InvalidOperationException

Browser authentication requires a desktop session

Error message

Browser authentication requires a desktop session

What it means

GetTokenByBrowserAsync first checks that the current session is a desktop session, because browser-based OAuth requires launching the user's default web browser. Without a desktop session it throws Trace2InvalidOperationException.

Solutions

  1. Use device code flow instead of browser flow for headless environments
  2. Run the command inside an interactive desktop session
  3. Configure GCM to use a credential helper / cached credential instead of interactive auth
  4. For WSL, ensure browser interop (e.g. wslview/BROWSER) is available so a desktop session is detected

Example fix

// before
var token = await oauth.GetTokenByBrowserAsync(client, scopes); // headless CI
// after
var token = await oauth.GetTokenByDeviceCodeAsync(client, scopes); // works headless
Defensive patterns

Strategy: type-guard

Validate before calling

if (!context.SessionManager.IsDesktopSession) throw new InvalidOperationException("Browser flow requires a desktop session; use device code flow.");

Type guard

bool CanUseBrowserFlow(CommandContext ctx) => ctx.SessionManager.IsDesktopSession;

Try / catch

try { token = await oauth.GetTokenByBrowserAsync(client, scopes); } catch (InvalidOperationException ex) when (ex.Message.Contains("desktop session")) { token = await oauth.GetTokenByDeviceCodeAsync(client, scopes); }

Prevention

When it happens

Trigger: Calling GetTokenByBrowserAsync (or triggering browser-mode OAuth) over SSH, in a headless service/CI job, in a container without a display, or on Windows over a non-interactive session where SessionManager.IsDesktopSession is false.

Common situations: git push/fetch with GCM in an SSH session; CI pipelines needing GitHub credentials; WSL without Windows browser interop configured; running cron/systemd services.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of git-ecosystem/git-credential-manager@e8ce762cd0 (2026-09-11). Data as JSON: /api/errors/add9a91505f9127c. Report an issue: GitHub.

Appendix: source

Thrown at src/Core/Authentication/OAuthAuthentication.cs:184

                    return OAuthAuthenticationModes.Browser;

                case "devicecode":
                    return OAuthAuthenticationModes.DeviceCode;

                default:
                    throw new Trace2Exception(Context.Trace2,
                        $"Unknown mode value in response '{responseMode}'");
            }
        }

        public async Task<OAuth2TokenResult> GetTokenByBrowserAsync(OAuth2Client client, string[] scopes)
        {
            ThrowIfUserInteractionDisabled();

            // We require a desktop session to launch the user's default web browser
            if (!Context.SessionManager.IsDesktopSession)
            {
                throw new Trace2InvalidOperationException(Context.Trace2,
                    "Browser authentication requires a desktop session");
            }

            var browserOptions = new OAuth2WebBrowserOptions();
            var browser = new OAuth2SystemWebBrowser(Context.SessionManager, browserOptions);
            var authCode = await client.GetAuthorizationCodeAsync(scopes, browser, CancellationToken.None);
            return await client.GetTokenByAuthorizationCodeAsync(authCode, CancellationToken.None);
        }

        public async Task<OAuth2TokenResult> GetTokenByDeviceCodeAsync(OAuth2Client client, string[] scopes)
        {
            ThrowIfUserInteractionDisabled();

            OAuth2DeviceCodeResult dcr = await client.GetDeviceCodeAsync(scopes, CancellationToken.None);

            // If we have a desktop session show the device code in a dialog
            if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession)
            {

View on GitHub (pinned to e8ce762cd0)