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

Browser authentication requires a desktop session

Error message

Browser authentication requires a desktop session

What it means

GetOAuthTokenViaBrowserAsync launches the user's default web browser for the GitLab OAuth2 flow, which is only possible in an interactive desktop session. When Context.SessionManager.IsDesktopSession is false, this Trace2InvalidOperationException is thrown instead of hanging or failing to open a browser.

Solutions

  1. Use a PAT instead of browser authentication (credential.gitLabAuthModes pat or `git-credential-manager gitlab login --pat`).
  2. Run the authentication step on a machine with a desktop session, then copy/transport the stored credential.
  3. If on SSH, port-forward and use an environment that can open a local browser, or set GCM to non-browser modes.

Example fix

// before (CI)
git-credential-manager gitlab login
// after
git-credential-manager gitlab login --pat
# or set
git config --global credential.gitLabAuthModes pat
Defensive patterns

Strategy: fallback

Validate before calling

// Check desktop session before requesting browser auth
if (!Environment.UserInteractive || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DISPLAY")) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
  // use PAT flow instead of browser flow
}

Try / catch

try {
  var token = await auth.GetOAuthTokenViaBrowserAsync(targetUri, scopes);
} catch (InvalidOperationException ex) when (ex.Message.Contains("desktop session")) {
  // fall back to PAT-based authentication
  var pat = await GetPatAsync();
}

Prevention

When it happens

Trigger: Calling GetOAuthTokenViaBrowserAsync (or a GitLab authentication flow that requests browser mode) from an SSH session, Windows service, CI pipeline, container, or headless cron job where no desktop session exists.

Common situations: Running git push with GitLab browser auth over SSH to a server; GitHub Actions/GitLab CI runners without GCM_INTERACTIVE or device-flow support; Docker containers doing git operations.

Understand the failure class

Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/GitLab/GitLabAuthentication.cs:270

                    return new AuthenticationPromptResult(
                        AuthenticationModes.Basic, new GitCredential(userName, password));

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

        public async Task<OAuth2TokenResult> GetOAuthTokenViaBrowserAsync(Uri targetUri, IEnumerable<string> scopes)
        {
            ThrowIfUserInteractionDisabled();

            var oauthClient = new GitLabOAuth2Client(HttpClient, Context.Settings, targetUri, Context.Trace2);

            // 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);

            // Write message to the terminal (if any is attached) for some feedback that we're waiting for a web response
            Context.Console.WriteInfo("please complete authentication in your browser...");

            OAuth2AuthorizationCodeResult authCodeResult =
                await oauthClient.GetAuthorizationCodeAsync(scopes, browser, CancellationToken.None);

            return await oauthClient.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None);
        }

        public async Task<OAuth2TokenResult> GetOAuthTokenViaRefresh(Uri targetUri, string refreshToken)
        {
            var oauthClient = new GitLabOAuth2Client(HttpClient, Context.Settings, targetUri, Context.Trace2);

View on GitHub (pinned to e8ce762cd0)