From 3ba1e95df4d71bba9ae4569f800bfe102d423d5d Mon Sep 17 00:00:00 2001
From: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Date: Sat, 30 May 2026 00:14:34 -0700
Subject: [PATCH] Add Codex status sign-in action (#3511)
* Add Codex status sign-in action
Implements the Codex status menu sign-in action and documents the design in docs/codex-status-sign-in-action.md.
* rm design doc
---
.../src/components/status-bar/StatusBar.tsx | 112 +++++++++++++++++-
1 file changed, 110 insertions(+), 2 deletions(-)
diff --git a/src/renderer/src/components/status-bar/StatusBar.tsx b/src/renderer/src/components/status-bar/StatusBar.tsx
index feda8c73787..a7b4cce58b6 100644
--- a/src/renderer/src/components/status-bar/StatusBar.tsx
+++ b/src/renderer/src/components/status-bar/StatusBar.tsx
@@ -7,11 +7,13 @@ import {
Plug,
ChevronDown,
ChevronRight,
+ Loader2,
PanelsTopLeft,
RefreshCw,
Server
} from 'lucide-react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import {
DropdownMenu,
@@ -858,6 +860,54 @@ function InlineUsageBars({
)
}
+function isUnavailableInactiveUsage(limits: ProviderRateLimits | null | undefined): boolean {
+ return limits?.status === 'error' && !limits.session && !limits.weekly
+}
+
+function InlineUsageSignInAction({
+ isFetching,
+ isSigningIn,
+ disabled,
+ onSignInPointerDown,
+ onSignIn
+}: {
+ isFetching: boolean
+ isSigningIn: boolean
+ disabled: boolean
+ onSignInPointerDown?: () => void
+ onSignIn: () => void
+}): React.JSX.Element {
+ return (
+
+ Sign in to see usage
+
+
+ )
+}
+
function InlineUsageSkeleton(): React.JSX.Element {
return (
@@ -995,6 +1045,17 @@ function CodexSwitcherMenu({
activeAccountId: null
})
const [isSwitching, setIsSwitching] = useState(false)
+ const [reauthenticatingAccountId, setReauthenticatingAccountId] = useState
(null)
+ const accountsExpandedRef = useRef(accountsExpanded)
+ // Why: Radix item selection is separate from the nested button click, so
+ // propagation stops alone do not prevent the row switch action.
+ const suppressNextAccountSelectRef = useRef(false)
+ const suppressNextAccountSelect = useCallback(() => {
+ suppressNextAccountSelectRef.current = true
+ window.setTimeout(() => {
+ suppressNextAccountSelectRef.current = false
+ }, 0)
+ }, [])
const openSettingsPage = useAppStore((s) => s.openSettingsPage)
const openSettingsTarget = useAppStore((s) => s.openSettingsTarget)
const fetchSettings = useAppStore((s) => s.fetchSettings)
@@ -1021,6 +1082,10 @@ function CodexSwitcherMenu({
setAccounts(next)
}, [])
+ useEffect(() => {
+ accountsExpandedRef.current = accountsExpanded
+ }, [accountsExpanded])
+
useEffect(() => {
// Why: the status bar keeps its own lightweight account snapshot for the
// dropdown. Settings account actions mutate the main-process store outside
@@ -1035,7 +1100,7 @@ function CodexSwitcherMenu({
accountId: string | null,
target: CodexStatusRuntimeTarget
): Promise => {
- if (isSwitching) {
+ if (isSwitching || reauthenticatingAccountId !== null) {
return
}
const previousActiveAccountId = getCodexStatusActiveId(accountState, target)
@@ -1068,6 +1133,26 @@ function CodexSwitcherMenu({
}
}
+ const handleSignInAccount = async (accountId: string): Promise => {
+ if (isSwitching || reauthenticatingAccountId !== null) {
+ return
+ }
+ setReauthenticatingAccountId(accountId)
+ try {
+ const next = await window.api.codexAccounts.reauthenticate({ accountId })
+ recordFeatureInteraction('codex-account-switching')
+ setAccounts(next)
+ await fetchSettings()
+ if (accountsExpandedRef.current) {
+ await fetchInactiveCodexAccountUsage()
+ }
+ } catch (error) {
+ console.error('Failed to re-authenticate Codex account from status bar:', error)
+ } finally {
+ setReauthenticatingAccountId(null)
+ }
+ }
+
const handleSelectRuntime = async (group: CodexStatusSwitchGroup): Promise => {
const currentKey = getCodexStatusRuntimeKey(
normalizeCodexStatusRuntimeTarget(accountState, toCodexStatusRuntimeTarget(codexTarget))
@@ -1161,6 +1246,12 @@ function CodexSwitcherMenu({
const inactiveUsage = target.id
? inactiveCodexAccounts.find((a) => a.accountId === target.id)
: null
+ const showSignInAction =
+ !target.active &&
+ target.id !== null &&
+ isUnavailableInactiveUsage(inactiveUsage?.claude)
+ const isSigningIn = reauthenticatingAccountId === target.id
+ const isBusy = isSwitching || reauthenticatingAccountId !== null
return (
@@ -1188,6 +1283,19 @@ function CodexSwitcherMenu({
{inactiveUsage?.isFetching && !inactiveUsage.claude ? (
+ ) : showSignInAction ? (
+
{
+ suppressNextAccountSelect()
+ if (target.id !== null) {
+ void handleSignInAccount(target.id)
+ }
+ }}
+ />
) : inactiveUsage?.claude ? (