{"record":{"id":"a0dd87472e8d3f22","repo":"charmbracelet/crush","slug":"interactive-oauth-authorization-required","errorCode":null,"errorMessage":"interactive OAuth authorization required","messagePattern":"interactive OAuth authorization required","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/oauth/mcp/handler.go","lineNumber":32,"sourceCode":"\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/charmbracelet/crush/internal/oauth\"\n\t\"github.com/charmbracelet/crush/internal/oauth/callback\"\n\t\"github.com/modelcontextprotocol/go-sdk/auth\"\n\t\"github.com/modelcontextprotocol/go-sdk/oauthex\"\n\t\"github.com/pkg/browser\"\n\t\"golang.org/x/oauth2\"\n)\n\n// ErrInteractiveAuthRequired is returned by Authorize when a server needs\n// interactive (browser) authorization but the current context does not\n// permit it. Background connections such as startup deliberately withhold\n// permission so a failed or missing token surfaces as a needs-auth state\n// instead of silently opening a browser and blocking initialization. The\n// user then triggers the interactive flow explicitly.\nvar ErrInteractiveAuthRequired = errors.New(\"interactive OAuth authorization required\")\n\n// interactiveKey marks a context as permitting the interactive browser flow.\ntype interactiveKey struct{}\n\n// WithInteractive returns a context that permits the interactive browser\n// authorization flow. Only user-initiated authentication should use it.\nfunc WithInteractive(ctx context.Context) context.Context {\n\treturn context.WithValue(ctx, interactiveKey{}, true)\n}\n\n// IsInteractive reports whether ctx permits the interactive browser flow.\nfunc IsInteractive(ctx context.Context) bool {\n\tv, _ := ctx.Value(interactiveKey{}).(bool)\n\treturn v\n}\n\n// callbackPath is the path the authorization server redirects back to. It\n// is part of the registered redirect URI, so it must not change without","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/oauth/mcp/handler.go#L14-L50","documentation":"ErrInteractiveAuthRequired is returned by the MCP OAuth handler's Authorize when a server needs browser-based interactive authorization but the current context forbids it. Background connections (e.g. at startup) deliberately withhold the interactive permission so a missing token surfaces as a needs-auth state instead of silently opening a browser and blocking initialization.","triggerScenarios":"Calling Authorize on an MCP OAuth handler without a context created via WithInteractive when the server has no valid token and must do a fresh browser flow.","commonSituations":"Crush starts up, connects to an MCP server whose stored token is expired/absent, and the background authorize path refuses to open a browser; the user must run the interactive auth flow explicitly (e.g. via /mcp auth command).","solutions":["Detect the sentinel with errors.Is(err, mcp.ErrInteractiveAuthRequired) and surface a 'needs authentication' state to the user.","Re-run Authorize with a context from mcp.WithInteractive(ctx) when the user explicitly initiates login.","Pre-authenticate the MCP server interactively before relying on background connections."],"exampleFix":"// before\nif err := handler.Authorize(ctx); err != nil { return err }\n// after\nif err := handler.Authorize(ctx); err != nil {\n    if errors.Is(err, mcp.ErrInteractiveAuthRequired) {\n        ui.PromptMcpLogin(server) // user-triggered\n        ctx = mcp.WithInteractive(ctx)\n        return handler.Authorize(ctx)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// Only authorize interactively from user-initiated paths:\n// ctx := mcp.WithInteractive(ctx) before calling Authorize in UI code.","typeGuard":"func isInteractiveAuthRequired(err error) bool {\n    return errors.Is(err, mcp.ErrInteractiveAuthRequired)\n}","tryCatchPattern":"if err := handler.Authorize(ctx); err != nil {\n    if errors.Is(err, mcp.ErrInteractiveAuthRequired) {\n        return ErrNeedsUserAuth // surface needs-auth state\n    }\n    return err\n}","preventionTips":["Never call Authorize with a non-interactive context from startup code.","Route all browser-based logins through explicit user commands.","Render a needs-auth state in the UI when this sentinel surfaces."],"tags":["oauth","mcp","interactive-auth"],"backgroundTag":"interactive-auth-required","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}