From c36425b6ebecc3e05a212565f94f3eb8eb625b7e Mon Sep 17 00:00:00 2001 From: masnwilliams <43387599+masnwilliams@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:28:25 +0000 Subject: [PATCH] Bound kernel login token exchange with a timeout The login flow used a context with no deadline and an HTTP client with no timeout for the OAuth token exchange, so a stalled /token (e.g. auth-side Redis down) left `kernel login` spinning until Ctrl-C. Cap the code exchange and the refresh request at 30s so they fail with a clear error instead of hanging. Co-Authored-By: Claude Opus 4.8 --- pkg/auth/oauth.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/auth/oauth.go b/pkg/auth/oauth.go index ebf6a9f3..42c4a83d 100644 --- a/pkg/auth/oauth.go +++ b/pkg/auth/oauth.go @@ -45,6 +45,10 @@ const ( // OAuth scopes - openid for the MCP server flow DefaultScope = "openid email" + + // tokenExchangeTimeout bounds the token/refresh HTTP calls so a stalled auth + // server surfaces as an error instead of hanging the CLI indefinitely. + tokenExchangeTimeout = 30 * time.Second ) // OAuthConfig represents the OAuth2 configuration @@ -311,6 +315,9 @@ func (oc *OAuthConfig) exchangeCodeForTokens(ctx context.Context, code, orgID st opts = append(opts, oauth2.SetAuthURLParam("org_id", orgID)) } + ctx, cancel := context.WithTimeout(ctx, tokenExchangeTimeout) + defer cancel() + token, err := oc.Config.Exchange(ctx, code, opts...) if err != nil { return nil, fmt.Errorf("failed to exchange code for token: %w", err) @@ -349,7 +356,7 @@ func RefreshTokens(ctx context.Context, tokens *TokenStorage) (*TokenStorage, er req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - client := &http.Client{} + client := &http.Client{Timeout: tokenExchangeTimeout} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to send refresh request: %w", err)