From 8f1dc4f6d3ccc77b587f4b947a0a13aec1a01144 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Sun, 5 Apr 2026 00:51:50 -0500 Subject: [PATCH] Hide auth failures from thread errors by default - Add a setting to suppress provider login errors in the banner - Default the new flag on and detect Codex/Claude auth failures - Make release prep changelog updates idempotent --- apps/web/src/appSettings.test.ts | 8 +++++ apps/web/src/appSettings.ts | 1 + apps/web/src/components/BranchToolbar.tsx | 16 ++++++++-- apps/web/src/components/ChatView.tsx | 1 + .../src/components/chat/ThreadErrorBanner.tsx | 7 ++++- .../src/components/chat/threadError.test.ts | 19 +++++++++++- apps/web/src/components/chat/threadError.ts | 19 ++++++++++++ apps/web/src/routes/_chat.settings.tsx | 31 +++++++++++++++++++ docs/releases/v0.14.0/assets.md | 14 ++++----- scripts/prepare-release.ts | 22 +++++++++++-- 10 files changed, 123 insertions(+), 15 deletions(-) diff --git a/apps/web/src/appSettings.test.ts b/apps/web/src/appSettings.test.ts index e1fd28d33..186e76217 100644 --- a/apps/web/src/appSettings.test.ts +++ b/apps/web/src/appSettings.test.ts @@ -19,6 +19,14 @@ import { resolveAppModelSelection, } from "./appSettings"; +describe("AppSettingsSchema", () => { + it("defaults auth failure errors to enabled", () => { + const settings = Schema.decodeUnknownSync(AppSettingsSchema)({}); + + expect(settings.showAuthFailuresAsErrors).toBe(true); + }); +}); + describe("normalizeCustomModelSlugs", () => { it("normalizes aliases, removes built-ins, and deduplicates values", () => { expect( diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 47dd04bf1..afe8e50fe 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -70,6 +70,7 @@ export const AppSettingsSchema = Schema.Struct({ autoDeleteMergedThreadsDelayMinutes: Schema.Number.pipe(withDefaults(() => 5)), diffWordWrap: Schema.Boolean.pipe(withDefaults(() => false)), enableAssistantStreaming: Schema.Boolean.pipe(withDefaults(() => false)), + showAuthFailuresAsErrors: Schema.Boolean.pipe(withDefaults(() => true)), locale: AppLocale.pipe(withDefaults(() => DEFAULT_APP_LOCALE)), openLinksExternally: Schema.Boolean.pipe(withDefaults(() => false)), sidebarProjectSortOrder: SidebarProjectSortOrder.pipe( diff --git a/apps/web/src/components/BranchToolbar.tsx b/apps/web/src/components/BranchToolbar.tsx index c3a2d7050..d81676aa3 100644 --- a/apps/web/src/components/BranchToolbar.tsx +++ b/apps/web/src/components/BranchToolbar.tsx @@ -3,7 +3,12 @@ import { ArrowDownIcon, FolderIcon, GitForkIcon, LoaderIcon } from "lucide-react import { useCallback, useEffect } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { gitPullMutationOptions, gitQueryKeys, gitStatusQueryOptions, invalidateGitQueries } from "../lib/gitReactQuery"; +import { + gitPullMutationOptions, + gitQueryKeys, + gitStatusQueryOptions, + invalidateGitQueries, +} from "../lib/gitReactQuery"; import { newCommandId } from "../lib/utils"; import { readNativeApi } from "../nativeApi"; import { useComposerDraftStore } from "../composerDraftStore"; @@ -222,14 +227,19 @@ export default function BranchToolbar({ )} Pull - + {behindCount} } /> - Local branch is {behindCount} commit{behindCount !== 1 ? "s" : ""} behind upstream. Pull to update before starting a new thread. + Local branch is {behindCount} commit{behindCount !== 1 ? "s" : ""} behind upstream. + Pull to update before starting a new thread. ) : null} diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 2df5ec6c0..8ebc6aa05 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -4646,6 +4646,7 @@ export default function ChatView({ threadId }: ChatViewProps) { setThreadError(activeThread.id, null)} /> {/* Main content area with optional plan sidebar */} diff --git a/apps/web/src/components/chat/ThreadErrorBanner.tsx b/apps/web/src/components/chat/ThreadErrorBanner.tsx index f9bc91486..9f9b54384 100644 --- a/apps/web/src/components/chat/ThreadErrorBanner.tsx +++ b/apps/web/src/components/chat/ThreadErrorBanner.tsx @@ -1,16 +1,21 @@ import { memo } from "react"; import { Alert, AlertAction, AlertDescription, AlertTitle } from "../ui/alert"; import { CircleAlertIcon, XIcon } from "lucide-react"; -import { humanizeThreadError } from "./threadError"; +import { humanizeThreadError, isAuthenticationThreadError } from "./threadError"; export const ThreadErrorBanner = memo(function ThreadErrorBanner({ error, + showAuthFailuresAsErrors = true, onDismiss, }: { error: string | null; + showAuthFailuresAsErrors?: boolean; onDismiss?: () => void; }) { if (!error) return null; + if (!showAuthFailuresAsErrors && isAuthenticationThreadError(error)) { + return null; + } const presentation = humanizeThreadError(error); return (
diff --git a/apps/web/src/components/chat/threadError.test.ts b/apps/web/src/components/chat/threadError.test.ts index 3365cd733..bfb3e4d6d 100644 --- a/apps/web/src/components/chat/threadError.test.ts +++ b/apps/web/src/components/chat/threadError.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { humanizeThreadError } from "./threadError"; +import { humanizeThreadError, isAuthenticationThreadError } from "./threadError"; describe("humanizeThreadError", () => { it("summarizes worktree creation failures into a user-facing message", () => { @@ -22,4 +22,21 @@ describe("humanizeThreadError", () => { technicalDetails: null, }); }); + + it("detects provider authentication failures", () => { + expect( + isAuthenticationThreadError( + "Codex CLI is not authenticated. Run `codex login` and try again.", + ), + ).toBe(true); + expect( + isAuthenticationThreadError( + "Claude is not authenticated. Run `claude auth login` and try again.", + ), + ).toBe(true); + }); + + it("does not classify unrelated failures as authentication errors", () => { + expect(isAuthenticationThreadError("Provider crashed while starting.")).toBe(false); + }); }); diff --git a/apps/web/src/components/chat/threadError.ts b/apps/web/src/components/chat/threadError.ts index ac862084e..5b4ab9984 100644 --- a/apps/web/src/components/chat/threadError.ts +++ b/apps/web/src/components/chat/threadError.ts @@ -5,6 +5,15 @@ export interface ThreadErrorPresentation { } const WORKTREE_COMMAND_PREFIX = "Git command failed in GitCore.createWorktree:"; +const AUTH_FAILURE_PATTERNS = [ + "run `codex login`", + "run codex login", + "run `claude auth login`", + "run claude auth login", + "codex cli is not authenticated", + "claude is not authenticated", + "authentication required", +] as const; function extractWorktreeDetail(error: string): string | null { if (!error.startsWith(WORKTREE_COMMAND_PREFIX)) { @@ -16,6 +25,16 @@ function extractWorktreeDetail(error: string): string | null { return detail.length > 0 ? detail : null; } +export function isAuthenticationThreadError(error: string | null | undefined): boolean { + const trimmed = error?.trim(); + if (!trimmed) { + return false; + } + + const lower = trimmed.toLowerCase(); + return AUTH_FAILURE_PATTERNS.some((pattern) => lower.includes(pattern)); +} + export function humanizeThreadError(error: string): ThreadErrorPresentation { const trimmed = error.trim(); const worktreeDetail = extractWorktreeDetail(trimmed); diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index 2e62fd684..8a3ebd9f7 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -415,6 +415,9 @@ function SettingsRouteView() { ...(settings.enableAssistantStreaming !== defaults.enableAssistantStreaming ? ["Assistant output"] : []), + ...(settings.showAuthFailuresAsErrors !== defaults.showAuthFailuresAsErrors + ? ["Auth failure errors"] + : []), ...(settings.openLinksExternally !== defaults.openLinksExternally ? ["Open links externally"] : []), @@ -1207,6 +1210,34 @@ function SettingsRouteView() { } /> + + updateSettings({ + showAuthFailuresAsErrors: defaults.showAuthFailuresAsErrors, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + showAuthFailuresAsErrors: Boolean(checked), + }) + } + aria-label="Show authentication failures as thread errors" + /> + } + /> +