From fa0ff8d7d8a6d496faeb846d39c02e124040b73d Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:53:56 -0700 Subject: [PATCH 1/3] fix(native-chat): a resting chat lists its current model as a live child does, with no phantom effort A chat at rest (no live child) answered its options from the host's model catalog alone. When the chat's model is one the catalog does not list, the answer left it out, the client filled the gap from its static seed, and the composer showed an effort control ("Medium") that the live child never offers; the control vanished again once the child was back. The live Claude and Codex answers and the resting answer now build their model list through one function: the catalog's rows, plus the current model when the catalog does not list it, with no effort levels. Live and rest can no longer disagree about it. --- ...-structured-effort-default-at-rest.test.ts | 67 +++++++++++++++++-- .../claude-structured-session-options.ts | 12 ++-- .../codex/codex-structured-model-catalog.ts | 15 ++--- .../structured-agent-session-option-models.ts | 19 ++++++ .../structured-agent-session-options-read.ts | 3 +- 5 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 src/main/native-chat/agent-session-wire/structured-agent-session-option-models.ts diff --git a/src/main/claude/claude-structured-effort-default-at-rest.test.ts b/src/main/claude/claude-structured-effort-default-at-rest.test.ts index 2175ef49e435..a44fce0c6a6b 100644 --- a/src/main/claude/claude-structured-effort-default-at-rest.test.ts +++ b/src/main/claude/claude-structured-effort-default-at-rest.test.ts @@ -14,6 +14,7 @@ import { agentModelCatalogFingerprintForRecord } from '../native-chat/agent-mode import { AgentModelCatalogStore } from '../native-chat/agent-model-catalog/agent-model-catalog-store' import type { StructuredAgentSessionMutationContext } from '../native-chat/agent-session-wire/structured-agent-session-host-mutations' import { readStructuredAgentSessionOptions } from '../native-chat/agent-session-wire/structured-agent-session-options-read' +import { composeCodexSessionOptionCatalog } from '../codex/codex-structured-model-catalog' import { nativeSessionOptionsFromReport } from '../native-chat/agent-session-wire/structured-agent-session-option-restoration' import { ClaudeStructuredSessionAdapter, @@ -55,16 +56,18 @@ const CATALOG = [ } ] -/** get_settings with nothing overriding the CLI's own default; an effort write moves both views. */ -function settingsWithNoOverride() { +/** get_settings with nothing overriding the CLI's own default; an effort write moves both views. + * `model` is what the CLI says it runs. */ +function settingsWithNoOverride(model = 'claude-opus-5-5[1m]') { const effective: { effortLevel?: string } = {} const settings = { effective, sources: [], - applied: { model: 'claude-opus-5-5[1m]', effort: 'medium', advisor: null, ultracode: false } + applied: { model, effort: 'medium', advisor: null, ultracode: false } } const claude = fakeClaude({ initProof: 'session-start', + initModel: model, initModels: CATALOG, settings, routes: { @@ -88,7 +91,8 @@ function settingsWithNoOverride() { async function startChild( store: AgentModelCatalogStore, options?: Record, - events: ClaudeStructuredSessionEvent[] = [] + events: ClaudeStructuredSessionEvent[] = [], + runs?: string ): Promise { const adapter = new ClaudeStructuredSessionAdapter({ resolveLaunch: async () => ({ @@ -102,7 +106,7 @@ async function startChild( continuesChain: false }), onEvent: (event) => events.push(event), - openConnection: settingsWithNoOverride().openConnection, + openConnection: settingsWithNoOverride(runs).openConnection, readProcessStartTime: async () => 1_700_000_000_000, now: () => 1_700_000_000_500, persistHandle: async () => {}, @@ -206,6 +210,59 @@ describe('Claude effort default at rest', () => { expect(catalogDefault(other, 'opus[1m]')).toBe('medium') }) + it('offers no effort for a model the catalog does not list, at rest as live', async () => { + const store = new AgentModelCatalogStore() + const unlisted = { model: 'claude-unlisted-9', effort: 'medium' } + // The CLI runs a model its own catalog does not list, as a newer or pinned model can be. + const child = await startChild(store, unlisted, [], unlisted.model) + const live = await child.readOptions({ sessionId: SESSION, fence: 7 }) + + const resting = await readAtRest(store, restingRecord(unlisted)) + + const row = (result: typeof resting) => + result.models.find((entry) => entry.id === unlisted.model) + expect(row(live)).toEqual(expect.objectContaining({ id: unlisted.model, efforts: [] })) + expect(row(resting)).toEqual(row(live)) + expect(pickerEffort(live)).toBeUndefined() + expect(pickerEffort(resting)).toBeUndefined() + }) + + it('lists an unlisted Codex model at rest exactly as its live child does', async () => { + const store = new AgentModelCatalogStore() + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the resting read and the catalog key touch only these fields. + const record = { + provider: 'codex', + accountHome: { variable: 'CODEX_HOME', path: '/accounts/codex' }, + location: { wslDistro: null }, + options: { model: 'gpt-unlisted', effort: 'high' } + } as unknown as AgentSessionRecord + const listing = { + models: [ + { + id: 'gpt-5.5', + label: 'GPT-5.5', + isDefault: true, + efforts: [ + { value: 'medium', label: 'Medium' }, + { value: 'high', label: 'High' } + ] + } + ], + fastModeTierByModel: new Map() + } + store.recordSuccess(agentModelCatalogFingerprintForRecord(record), 'codex', { + ...listing, + origin: 'live-session' + }) + + const resting = await readAtRest(store, record) + const live = composeCodexSessionOptionCatalog(listing, { + current: { model: 'gpt-unlisted', effort: 'high' } + }).result + + expect(resting.models).toEqual(live.models) + }) + it("leaves a Codex chat's unsaved effort blank at rest, as its live child does", async () => { const store = new AgentModelCatalogStore() // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the resting read and the catalog key touch only these fields. diff --git a/src/main/claude/claude-structured-session-options.ts b/src/main/claude/claude-structured-session-options.ts index f4c59148241b..fdb80145b1c0 100644 --- a/src/main/claude/claude-structured-session-options.ts +++ b/src/main/claude/claude-structured-session-options.ts @@ -13,6 +13,7 @@ import { type ListedModel } from './claude-structured-model-catalog' import type { ClaudeSession } from './claude-structured-session-state' +import { structuredAgentSessionOptionModels } from '../native-chat/agent-session-wire/structured-agent-session-option-models' import { decodeStructuredAgentSessionOptionValue } from '../../shared/structured-agent-session-option-codec' /** @@ -326,12 +327,13 @@ export function claudeStructuredSessionOptionsFrom( ): AgentSessionOptionsResult { const discovered = listedModels(catalog ? { models: catalog } : null) writeClaudeCatalogThrough(session, discovered) - const models = discovered.length > 0 ? discovered : seedModels() + const listed = discovered.length > 0 ? discovered : seedModels() const current = readClaudeCurrentModel(session) - const model = currentModelId(models, current.id) - if (!models.some((entry) => entry.id === model)) { - models.push({ id: model, label: model, isDefault: false, efforts: [], resolvedModel: null }) - } + const model = currentModelId(listed, current.id) + const models = structuredAgentSessionOptionModels(listed, model, (row) => ({ + ...row, + resolvedModel: null + })) const effort = session.options.get('effort') ?? session.reportedOptions.effort ?? diff --git a/src/main/codex/codex-structured-model-catalog.ts b/src/main/codex/codex-structured-model-catalog.ts index c309fcaaaf63..e8164494dfd8 100644 --- a/src/main/codex/codex-structured-model-catalog.ts +++ b/src/main/codex/codex-structured-model-catalog.ts @@ -4,6 +4,7 @@ import type { AgentSessionOptionsResult } from '../../shared/agent-session-wire' import type { CodexAppServerConnection } from './codex-app-server-connection' +import { structuredAgentSessionOptionModels } from '../native-chat/agent-session-wire/structured-agent-session-option-models' import { codexFastModeSupport, readCodexFastModeTier } from './codex-structured-fast-mode' import { applyCodexConfiguredLaunchDefaults, @@ -141,15 +142,11 @@ export function composeCodexSessionOptionCatalog( reportedServiceTierKnown?: boolean } ): CodexSessionOptionCatalog { - const models = listing.models.map((entry) => ({ ...entry })) - if (input.current.model && !models.some((model) => model.id === input.current.model)) { - models.push({ - id: input.current.model, - label: input.current.model, - isDefault: false, - efforts: [] - }) - } + const models = structuredAgentSessionOptionModels( + listing.models.map((entry) => ({ ...entry })), + input.current.model, + (row) => row + ) const model = input.current.model ?? models.find((entry) => entry.isDefault)?.id ?? models[0]?.id if (!model) { throw new Error('codex app-server returned no available models') diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-option-models.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-option-models.ts new file mode 100644 index 000000000000..1c8ec0baa4bf --- /dev/null +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-option-models.ts @@ -0,0 +1,19 @@ +// The models a session's options answer lists, the same whether a child answers live or the host +// answers for a chat at rest: the catalog's rows, plus the current model when the catalog does not +// list it. Nothing says which efforts an unlisted model takes, so its row offers none, and a client +// shows no effort control for it. + +import type { AgentSessionModelOption } from '../../../shared/agent-session-wire' + +/** `row` builds a provider's own row type from the shared one. */ +export function structuredAgentSessionOptionModels( + catalog: readonly TModel[], + current: string | undefined, + row: (unlisted: AgentSessionModelOption) => TModel +): TModel[] { + const models = [...catalog] + if (current && !models.some((entry) => entry.id === current)) { + models.push(row({ id: current, label: current, isDefault: false, efforts: [] })) + } + return models +} diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts index 3bad8d9789f1..22e11114df14 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts @@ -16,6 +16,7 @@ import { journalOpenReadRefusal } from '../agent-session-journal/journal-open-fa import { isClaudeStructuredOptionKey } from '../../claude/claude-structured-options' import { isCodexTurnOptionKey } from '../../codex/codex-structured-turn-start' import type { StructuredAgentSessionHostDeps } from './structured-agent-session-host-types' +import { structuredAgentSessionOptionModels } from './structured-agent-session-option-models' import type { AgentSessionTurnContext, TurnOutcome } from './structured-agent-session-turns' import type { StructuredAgentSessionMutationContext } from './structured-agent-session-host-mutations' @@ -48,7 +49,7 @@ async function readStructuredAgentSessionOptionsAtRest( ? models.find((entry) => entry.id === model)?.defaultEffort : undefined) return { - models, + models: structuredAgentSessionOptionModels(models, model, (row) => row), ...(catalog.origin !== 'unknown' && catalog.fastModeSupport ? { fastModeSupport: catalog.fastModeSupport } : {}), From 2cedb9d66c109d52787b00dcbc056bc0fa3216ce Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:09:02 -0700 Subject: [PATCH 2/3] fix(native-chat): a resting chat with no catalog yet lists what a live child would, so a listed model keeps its effort When the host has no model catalog for the account yet (the first read before its probe returns, a failing probe, an account-home lookup that failed, or a WSL-pinned chat no live child has written one for), the resting answer listed only the saved model, as unlisted, and a listed model such as opus lost its effort control. It now rests on the list a running child falls back to: Claude's built-in models, then the shared function. Codex lists nothing without its catalog, so the resting answer leaves the list to the client's own defaults, as a failed live read does. --- ...-structured-effort-default-at-rest.test.ts | 55 +++++++++++++++---- .../claude-structured-session-options.ts | 6 ++ .../structured-agent-session-options-read.ts | 18 +++++- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/main/claude/claude-structured-effort-default-at-rest.test.ts b/src/main/claude/claude-structured-effort-default-at-rest.test.ts index a44fce0c6a6b..9bf7523b7a75 100644 --- a/src/main/claude/claude-structured-effort-default-at-rest.test.ts +++ b/src/main/claude/claude-structured-effort-default-at-rest.test.ts @@ -57,8 +57,8 @@ const CATALOG = [ ] /** get_settings with nothing overriding the CLI's own default; an effort write moves both views. - * `model` is what the CLI says it runs. */ -function settingsWithNoOverride(model = 'claude-opus-5-5[1m]') { + * `model` is what the CLI says it runs, and `listing` the models it lists. */ +function settingsWithNoOverride(model = 'claude-opus-5-5[1m]', listing: unknown[] = CATALOG) { const effective: { effortLevel?: string } = {} const settings = { effective, @@ -68,10 +68,10 @@ function settingsWithNoOverride(model = 'claude-opus-5-5[1m]') { const claude = fakeClaude({ initProof: 'session-start', initModel: model, - initModels: CATALOG, + initModels: listing, settings, routes: { - list_models: () => CATALOG, + list_models: () => listing, apply_flag_settings: (params) => { const written = params?.settings const effort = @@ -92,7 +92,7 @@ async function startChild( store: AgentModelCatalogStore, options?: Record, events: ClaudeStructuredSessionEvent[] = [], - runs?: string + cli: { runs?: string; lists?: unknown[] } = {} ): Promise { const adapter = new ClaudeStructuredSessionAdapter({ resolveLaunch: async () => ({ @@ -106,7 +106,7 @@ async function startChild( continuesChain: false }), onEvent: (event) => events.push(event), - openConnection: settingsWithNoOverride(runs).openConnection, + openConnection: settingsWithNoOverride(cli.runs, cli.lists).openConnection, readProcessStartTime: async () => 1_700_000_000_000, now: () => 1_700_000_000_500, persistHandle: async () => {}, @@ -156,10 +156,10 @@ function readAtRest(store: AgentModelCatalogStore, record: AgentSessionRecord) { return readStructuredAgentSessionOptions(context, SESSION) } -function pickerEffort(result: Awaited>) { - const seed = getAgentSessionOptionCatalog('claude')! +function pickerEffort(result: Awaited>, agent = 'claude') { + const seed = getAgentSessionOptionCatalog(agent)! const state = applyStructuredAgentSessionOptions( - createStructuredAgentSessionOptionState('claude', seed), + createStructuredAgentSessionOptionState(agent, seed), seed, result ) @@ -214,7 +214,7 @@ describe('Claude effort default at rest', () => { const store = new AgentModelCatalogStore() const unlisted = { model: 'claude-unlisted-9', effort: 'medium' } // The CLI runs a model its own catalog does not list, as a newer or pinned model can be. - const child = await startChild(store, unlisted, [], unlisted.model) + const child = await startChild(store, unlisted, [], { runs: unlisted.model }) const live = await child.readOptions({ sessionId: SESSION, fence: 7 }) const resting = await readAtRest(store, restingRecord(unlisted)) @@ -261,6 +261,41 @@ describe('Claude effort default at rest', () => { }).result expect(resting.models).toEqual(live.models) + expect(live.models.find((entry) => entry.id === 'gpt-unlisted')).toMatchObject({ efforts: [] }) + expect(pickerEffort(resting, 'codex')).toBeUndefined() + }) + + it("keeps a Claude model's effort at rest with no catalog yet, as a live child with none does", async () => { + const saved = { model: 'opus', effort: 'high' } + // A CLI that lists nothing: the live child falls back to Claude's built-in models. + const child = await startChild(new AgentModelCatalogStore(), saved, [], { + runs: 'opus', + lists: [] + }) + const live = await child.readOptions({ sessionId: SESSION, fence: 7 }) + + // An account the host has no catalog for yet. + const resting = await readAtRest(new AgentModelCatalogStore(), restingRecord(saved)) + + expect(resting.models).toEqual(live.models) + expect(pickerEffort(live)).toBe('high') + expect(pickerEffort(resting)).toBe('high') + }) + + it("keeps a Codex model's effort at rest with no catalog yet, from the client's own defaults", async () => { + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the resting read and the catalog key touch only these fields. + const record = { + provider: 'codex', + accountHome: { variable: 'CODEX_HOME', path: '/accounts/codex' }, + location: { wslDistro: null }, + options: { model: 'gpt-5.5', effort: 'high' } + } as unknown as AgentSessionRecord + + const resting = await readAtRest(new AgentModelCatalogStore(), record) + + // A Codex child lists nothing without its catalog, so the answer leaves the list to the client. + expect(resting.models).toEqual([]) + expect(pickerEffort(resting, 'codex')).toBe('high') }) it("leaves a Codex chat's unsaved effort blank at rest, as its live child does", async () => { diff --git a/src/main/claude/claude-structured-session-options.ts b/src/main/claude/claude-structured-session-options.ts index fdb80145b1c0..082f45726fe5 100644 --- a/src/main/claude/claude-structured-session-options.ts +++ b/src/main/claude/claude-structured-session-options.ts @@ -241,6 +241,12 @@ function wireClaudeModels(models: readonly ListedModel[]): WireClaudeModel[] { return models.map(wireClaudeModel) } +/** The built-in models a running child lists when the CLI gives it none; a chat at rest with no + * catalog lists the same. */ +export function claudeFallbackModelOptions(): WireClaudeModel[] { + return wireClaudeModels(seedModels()) +} + /** The listing, with what the CLI runs when no effort is sent on each model the child applies — * a default only a running child knows, and only while this session has no effort pick. */ function catalogClaudeModels(session: ClaudeSession, discovered: ListedModel[]): WireClaudeModel[] { diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts index 22e11114df14..18d0a301f015 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts @@ -7,9 +7,12 @@ import { refuse, + type AgentSessionModelOption, type AgentSessionOptionResult, type AgentSessionOptionsResult } from '../../../shared/agent-session-wire' +import type { AgentSessionRecord } from '../../../shared/agent-session-record' +import { claudeFallbackModelOptions } from '../../claude/claude-structured-session-options' import { decodeStructuredAgentSessionOptionValue } from '../../../shared/structured-agent-session-option-codec' import type { AgentSessionRecordStore } from '../../runtime/agent-session-record-store' import { journalOpenReadRefusal } from '../agent-session-journal/journal-open-failure' @@ -22,6 +25,15 @@ import type { StructuredAgentSessionMutationContext } from './structured-agent-s type RestingOptions = Pick +/** With no catalog for the account, the list a running child falls back to: Claude's built-in + * models. A Codex child answers nothing without its catalog, so none, and the client keeps its + * own defaults as it does when a live read fails. */ +function restingFallbackModels( + provider: AgentSessionRecord['provider'] +): AgentSessionModelOption[] | null { + return provider === 'claude' ? claudeFallbackModelOptions() : null +} + async function readStructuredAgentSessionOptionsAtRest( deps: Pick, sessionId: string @@ -33,7 +45,9 @@ async function readStructuredAgentSessionOptionsAtRest( const catalog = (await deps.modelCatalog ?.read({ agent: record.provider, sessionId }) .catch(() => null)) ?? { origin: 'unknown' as const } - const models = catalog.origin === 'unknown' ? [] : catalog.models + const listed = + catalog.origin === 'unknown' ? restingFallbackModels(record.provider) : catalog.models + const models = listed ?? [] const saved = record.options ?? {} const fastMode = saved.fastMode === undefined @@ -49,7 +63,7 @@ async function readStructuredAgentSessionOptionsAtRest( ? models.find((entry) => entry.id === model)?.defaultEffort : undefined) return { - models: structuredAgentSessionOptionModels(models, model, (row) => row), + models: listed ? structuredAgentSessionOptionModels(listed, model, (row) => row) : [], ...(catalog.origin !== 'unknown' && catalog.fastModeSupport ? { fastModeSupport: catalog.fastModeSupport } : {}), From c8e532e6306744408f1b085399966beb90ec0084 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:19:32 -0700 Subject: [PATCH 3/3] fix(native-chat): a resting chat with no pick and no catalog names no model, as before With no catalog, the built-in list's default model is a guess, not the account's: naming it moved a chat the running agent reported on Opus to Sonnet. Only a real listing names the default now; otherwise the answer names none and the client keeps the model the agent last reported. The comment on Codex's no-catalog answer now says what it does: the client fills the current model from its own defaults, unchanged from before. --- ...-structured-effort-default-at-rest.test.ts | 21 +++++++++++++++++++ .../structured-agent-session-options-read.ts | 12 +++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/main/claude/claude-structured-effort-default-at-rest.test.ts b/src/main/claude/claude-structured-effort-default-at-rest.test.ts index 9bf7523b7a75..0228b1171683 100644 --- a/src/main/claude/claude-structured-effort-default-at-rest.test.ts +++ b/src/main/claude/claude-structured-effort-default-at-rest.test.ts @@ -282,6 +282,27 @@ describe('Claude effort default at rest', () => { expect(pickerEffort(resting)).toBe('high') }) + it('names no model at rest with no pick and no catalog, so the client keeps what the child ran', async () => { + // The CLI's default model, reported live; the chat never picked one. + const child = await startChild(new AgentModelCatalogStore(), undefined, [], { + runs: 'opus', + lists: [] + }) + const live = await child.readOptions({ sessionId: SESSION, fence: 7 }) + + const resting = await readAtRest(new AgentModelCatalogStore(), restingRecord({})) + + // The built-in list's default is a guess, not this account's: nothing to name. + expect(resting.current.model).toBe('') + const seed = getAgentSessionOptionCatalog('claude')! + const state = [live, resting].reduce( + (current, answer) => applyStructuredAgentSessionOptions(current, seed, answer), + createStructuredAgentSessionOptionState('claude', seed) + ) + const model = structuredAgentSessionOptionSnapshot(state).find((row) => row.id === 'model') + expect(model?.kind.type === 'select' ? model.kind.currentValue : undefined).toBe('opus') + }) + it("keeps a Codex model's effort at rest with no catalog yet, from the client's own defaults", async () => { // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the resting read and the catalog key touch only these fields. const record = { diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts index 18d0a301f015..106998ccfed5 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts @@ -26,8 +26,8 @@ import type { StructuredAgentSessionMutationContext } from './structured-agent-s type RestingOptions = Pick /** With no catalog for the account, the list a running child falls back to: Claude's built-in - * models. A Codex child answers nothing without its catalog, so none, and the client keeps its - * own defaults as it does when a live read fails. */ + * models. A Codex child has no such list, so none: the client fills the current model from its + * own unknown-model defaults, unchanged from before this list was shared. */ function restingFallbackModels( provider: AgentSessionRecord['provider'] ): AgentSessionModelOption[] | null { @@ -53,8 +53,12 @@ async function readStructuredAgentSessionOptionsAtRest( saved.fastMode === undefined ? null : decodeStructuredAgentSessionOptionValue('fastMode', saved.fastMode) - // An unknown model is one the client already treats as unconfirmed. - const model = saved.model ?? models.find((entry) => entry.isDefault)?.id ?? '' + // An unknown model is one the client already treats as unconfirmed. Only a real listing names the + // account's default; a built-in list's default is a guess, so with none the client keeps its own. + const model = + saved.model ?? + (catalog.origin === 'unknown' ? undefined : models.find((entry) => entry.isDefault)?.id) ?? + '' // As a live child answers: the pick, else what Claude runs for this model when none is sent. // A live Codex child answers only the effort its thread reported, never the model's default. const effort =