diff --git a/web/src/components/AssistantChat/HappyComposer.tsx b/web/src/components/AssistantChat/HappyComposer.tsx
index e2639d2a..70b1f086 100644
--- a/web/src/components/AssistantChat/HappyComposer.tsx
+++ b/web/src/components/AssistantChat/HappyComposer.tsx
@@ -35,6 +35,7 @@ import { useTranslation } from '@/lib/use-translation'
import { getModelOptionsForFlavor, getNextModelForFlavor } from './modelOptions'
import { getClaudeComposerEffortOptions } from './claudeEffortOptions'
import { getCodexComposerReasoningEffortOptions } from './codexReasoningEffortOptions'
+import { getDisplayedCodexServiceTier } from './codexFastMode'
import { getPiThinkingLevelOptions, getHighestThinkingLevel, isThinkingLevelSupported } from './piThinkingLevelOptions'
import { groupModelsByProvider } from './piModelGroups'
import { PiModelPanel } from './PiModelPanel'
@@ -259,6 +260,7 @@ export function HappyComposer(props: {
const modelReasoningEffort = rawModelReasoningEffort ?? null
const effort = rawEffort ?? null
const serviceTier = rawServiceTier ?? null
+ const displayedServiceTier = getDisplayedCodexServiceTier(serviceTier)
const api = useAssistantApi()
const { composerEnterBehavior } = useComposerEnterBehavior()
@@ -1159,16 +1161,16 @@ export function HappyComposer(props: {
>
- {serviceTier === option.value && (
+ {displayedServiceTier === option.value && (
)}
-
+
{option.label}
@@ -1225,7 +1227,7 @@ export function HappyComposer(props: {
model,
modelReasoningEffort,
effort,
- serviceTier,
+ displayedServiceTier,
collaborationModeOptions,
permissionModeOptions,
handleCollaborationChange,
diff --git a/web/src/components/AssistantChat/codexFastMode.test.ts b/web/src/components/AssistantChat/codexFastMode.test.ts
index 88baf15f..f7f661bf 100644
--- a/web/src/components/AssistantChat/codexFastMode.test.ts
+++ b/web/src/components/AssistantChat/codexFastMode.test.ts
@@ -1,5 +1,9 @@
import { describe, expect, it } from 'vitest'
-import { codexModelAdvertisesFastTier, isFastServiceTier } from './codexFastMode'
+import {
+ codexModelAdvertisesFastTier,
+ getDisplayedCodexServiceTier,
+ isFastServiceTier
+} from './codexFastMode'
// Mirrors the real Codex catalog: the Fast tier's id is 'priority' and its
// display name is 'Fast', so the CLI captures both as lowercased tokens
@@ -49,3 +53,15 @@ describe('isFastServiceTier', () => {
expect(isFastServiceTier('standard')).toBe(false)
})
})
+
+describe('getDisplayedCodexServiceTier', () => {
+ it('displays untouched and explicit non-fast tiers as standard', () => {
+ expect(getDisplayedCodexServiceTier(null)).toBe('standard')
+ expect(getDisplayedCodexServiceTier(undefined)).toBe('standard')
+ expect(getDisplayedCodexServiceTier('standard')).toBe('standard')
+ })
+
+ it('preserves the fast selection', () => {
+ expect(getDisplayedCodexServiceTier('fast')).toBe('fast')
+ })
+})
diff --git a/web/src/components/AssistantChat/codexFastMode.ts b/web/src/components/AssistantChat/codexFastMode.ts
index 7d467d69..7b859990 100644
--- a/web/src/components/AssistantChat/codexFastMode.ts
+++ b/web/src/components/AssistantChat/codexFastMode.ts
@@ -49,3 +49,12 @@ export function codexModelAdvertisesFastTier(
export function isFastServiceTier(serviceTier?: string | null): boolean {
return serviceTier?.trim().toLowerCase() === 'fast'
}
+
+/**
+ * The persisted null tier means the user has not chosen an override. The
+ * current two-option control still needs a visible selection, so display that
+ * untouched state as Standard without changing the value sent to the backend.
+ */
+export function getDisplayedCodexServiceTier(serviceTier?: string | null): 'standard' | 'fast' {
+ return isFastServiceTier(serviceTier) ? 'fast' : 'standard'
+}