login: show auth host instead of the long authorize URL · Entire

login: show auth host instead of the long authorize URL

659f09f→main·

toothbrush·1mo ago·3 files·+29 added/-17 removed

The browser flow now prints "Logging in to: " rather than the full authorize URL (the PKCE challenge + loopback redirect made it long and unreadable). The full URL is shown only as a fallback when the browser can't be opened, which is also what a headless host hits.

openBrowser reports failure under test (no usable browser on a CI host, and we mustn't spawn a real one), so the integration test recovers the ephemeral loopback callback URL from that fallback line on stdout — replacing the file-based seam from the previous iteration.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

3ae392c3a1ebView transcript

Changes

3

332 unmodified lines

// waitForBrowserPrompt reads login stdout until it finds the "Login URL:"  
// line (the browser flow mirrors the device flow's prompt) and returns the  
// URL. The browser flow has no "Device code:" line, so this scans for the  
// URL alone rather than reusing waitForLoginPrompt.  
// waitForBrowserPrompt reads login stdout until it finds the  
// "Open this URL in your browser to sign in: <url>" fallback line and  
// returns the URL. Under test openBrowser reports failure (no usable  
// browser on a headless host), so the browser flow always prints this  
// fallback — which is how the test recovers the ephemeral callback URL.
func waitForBrowserPrompt(t *testing.T, stdout *bufio.Reader) string {

t.Helper()

const prefix = "Open this URL in your browser to sign in: "
    deadline := time.Now().Add(10 * time.Second)
    for time.Now().Before(deadline) {
        line, err := stdout.ReadString('\n')

if err != nil {
            t.Fatalf("read login output: %v", err)
        }
        line = strings.TrimSpace(line)
        if after, ok := strings.CutPrefix(line, "Login URL:"); ok {
            return strings.TrimSpace(after)
        }
        if after, ok := strings.CutPrefix(line, prefix); ok {
            return after
        }
    }
}

Mcmd/entire/cli/integration_test/login_test.go+8/-6

156 unmodified lines

// Login complete." runBrowserLogin is only reached interactively (see  
// shouldUseBrowserLogin), so the Enter prompt is unconditional here.

authURL := flow.AuthorizationURL()
fmt.Fprintf(outW, "Login URL:   %s\n\n", authURL)
fmt.Fprintf(outW, "Press Enter to open in browser...")
// Show the auth host, not the full authorize URL — the PKCE challenge +  
// loopback redirect make it long and unreadable, and the browser is  
// opened for the user anyway. The full URL is only printed below as a  
// fallback when the browser can't be opened.
fmt.Fprintf(outW, "Logging in to:  %s\n\n", client.BaseURL())
fmt.Fprint(outW, "Press Enter to open in browser...");

// Read from /dev/tty so we get a real keypress and don't consume piped stdin.
if err := waitForEnter(ctx); err != nil {
    return fmt.Errorf("wait for input: %w", err)
}

fmt.Fprintln(outW)

if err := openURL(ctx, authURL); err != nil {
    fmt.Fprintf(errW, "Warning: failed to open browser: %v\n", err)
    fmt.Fprintf(outW, "Open this URL in your browser to sign in: %s\n", authURL)
}

fmt.Fprint(outW, "Waiting for sign-in... ")
fmt.Fprint(outW, "\nWaiting for sign-in... ")

code, err := flow.Wait(ctx)
if err != nil {

return fmt.Errorf("refusing to open non-HTTP URL: %s", browserURL)
}

// Tests force interactive mode (ENTIRE_TEST_TTY=1) to exercise the  
// browser flow, but must not actually spawn a browser on the test/CI  
// host. Report success so the "Opening..." path runs — the loopback  
// listener is what the test drives. URL validation above still applies.
// Under test there's no usable browser, and we must not spawn a real one  
// on a dev/CI host. Report failure so the caller takes the "here's the  
// URL" fallback — exactly the path a genuinely headless machine hits, and  
// what lets an integration test recover the loopback callback URL from  
// stdout. URL validation above still applies.
if interactive.UnderTest() {
    return nil
}
return errors.New("browser unavailable under test")
}

var command string

Mcmd/entire/cli/login.go+14/-9

393 unmodified lines

if openedURL != flow.authURL {
    t.Errorf("opened URL = %q, want %q", openedURL, flow.authURL)
}
if !strings.Contains(out.String(), "Login URL:") || !strings.Contains(out.String(), flow.authURL) {
    t.Errorf("output missing login URL:\n%s", out.String())
// Happy path shows the auth host, not the full authorize URL, and  
// doesn't print the URL at all (the browser opened fine).
if !strings.Contains(out.String(), "Logging in to:") {
    t.Errorf("output missing 'Logging in to:' line:\n%s", out.String())
}
if strings.Contains(out.String(), flow.authURL) {
    t.Errorf("happy path should not print the full authorize URL:\n%s", out.String())
}
if !strings.Contains(out.String(), "Press Enter to open in browser...") {
    t.Errorf("output missing enter-to-open prompt:\n%s", out.String())
}

Mcmd/entire/cli/login_test.go+7/-2