Skip to content

Commit ef29aac

Browse files
authored
fix(auth): always show login URL so headless login can complete (#420)
On a machine with no display (e.g. an SSH'd server), login hung and timed out after 5 minutes. hasBrowser() returned true because xdg-open was installed, so browser.OpenURL was called; xdg-open printed "no DISPLAY environment variable specified" to stderr but exited 0, so OpenURL returned nil and the CLI printed "Waiting for login to complete in browser..." without ever showing the login URL. With no URL to visit, login could never complete. Follow Claude Code's pattern: always attempt to open the browser best-effort, but always print the login URL as a fallback so the user is never stranded. Discard the launcher's stderr (pkg/browser pipes it to ours) to suppress the confusing "no DISPLAY" noise. This removes the need for platform/display detection, so hasBrowser() is deleted.
1 parent f636937 commit ef29aac

1 file changed

Lines changed: 16 additions & 25 deletions

File tree

pkg/auth/auth.go

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import (
44
"bufio"
55
"errors"
66
"fmt"
7+
"io"
78
"os"
8-
"os/exec"
9-
"runtime"
109
"strings"
1110

1211
"github.com/brevdev/brev-cli/pkg/config"
@@ -324,10 +323,20 @@ func (t Auth) LoginWithAPIKey(apiKey string, orgID string) error {
324323
return nil
325324
}
326325

327-
// showLoginURL displays the login link and CLI alternative for manual navigation.
326+
func init() {
327+
// pkg/browser pipes the launcher's stderr to ours, which leaks confusing
328+
// noise like "xdg-open: no DISPLAY environment variable specified" on
329+
// headless machines. We always print the login URL, so discard it.
330+
browser.Stderr = io.Discard
331+
}
332+
333+
// showLoginURL prints the login link, framed as a fallback for when the
334+
// browser doesn't open (e.g. headless machine, wrong default browser).
328335
func showLoginURL(url string) {
329336
urlType := color.New(color.FgCyan, color.Bold).SprintFunc()
330-
fmt.Println("Login here: " + urlType(url))
337+
fmt.Println("Browser didn't open? Use the URL below to sign in:")
338+
fmt.Println()
339+
fmt.Println(urlType(url))
331340
}
332341

333342
func defaultAuthFunc(url, code string) {
@@ -337,12 +346,9 @@ func defaultAuthFunc(url, code string) {
337346
fmt.Print("\n")
338347
}
339348

340-
if hasBrowser() {
341-
if err := browser.OpenURL(url); err == nil {
342-
fmt.Println("Waiting for login to complete in browser...")
343-
return
344-
}
345-
}
349+
// Best-effort: try to open the browser, but always show the URL below so
350+
// the user is never stranded if it doesn't open.
351+
_ = browser.OpenURL(url)
346352
showLoginURL(url)
347353
fmt.Println("\nWaiting for login to complete...")
348354
}
@@ -352,21 +358,6 @@ func skipBrowserAuthFunc(url, _ string) {
352358
fmt.Println("\nWaiting for login to complete...")
353359
}
354360

355-
// hasBrowser reports whether a browser can be opened on the current platform.
356-
func hasBrowser() bool {
357-
if runtime.GOOS == "darwin" {
358-
// macOS always has "open".
359-
return true
360-
}
361-
// Linux: check for a known browser launcher.
362-
for _, name := range []string{"xdg-open", "x-www-browser", "www-browser"} {
363-
if _, err := exec.LookPath(name); err == nil {
364-
return true
365-
}
366-
}
367-
return false
368-
}
369-
370361
func (t Auth) Login(skipBrowser bool) (*LoginTokens, error) {
371362
authFunc := defaultAuthFunc
372363
if skipBrowser {

0 commit comments

Comments
 (0)