Repository navigation
fix(claude): follow the session model after a refusal fallback #24126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
57c6a76
5dc9695
8f1e7ea
9d6d843
a43e78f
789f266
3d9d7b1
502f1c7
f2add93
48528e8
da93dd9
1500421
61a75aa
a235cfe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4788,6 +4788,7 @@ interface ClaudeLiveQueryContext { | |
| readonly query: ClaudeAgentSdkQuerySession; | ||
| readonly queryPolicyKey: string; | ||
| selection: CompiledClaudeModelSelection; | ||
| modelSelection: ModelSelection; | ||
| readonly closed: Deferred.Deferred<void, never>; | ||
| lastTurn: ActiveClaudeTurnContext | null; | ||
| // "early" echoes on the first frame; "acknowledged" confirms uuid acceptance, but resume turns can still precede the prompt. | ||
|
|
@@ -10322,6 +10323,25 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | |
| } | ||
| const now = yield* DateTime.now; | ||
| const nativeItemId = message.uuid; | ||
| // "local" covers a subagent or side question; absent (older CLIs) means "session". | ||
| if (message.subtype === "model_refusal_fallback" && message.scope !== "local") { | ||
| const previousModelSelection = liveQuery.modelSelection; | ||
| const modelSelection = { | ||
| ...previousModelSelection, | ||
| model: resolveClaudeModelSlug(modelCatalog, message.fallback_model), | ||
| }; | ||
| liveQuery.selection = compileClaudeModelSelection(modelSelection, modelCatalog); | ||
| liveQuery.modelSelection = modelSelection; | ||
| yield* emitProviderEvent({ | ||
| type: "app_thread.model_selection.updated", | ||
| runId: context.input.runId, | ||
| attemptId: context.input.attemptId, | ||
| driver: CLAUDE_PROVIDER, | ||
| threadId: context.input.threadId, | ||
| previousModelSelection, | ||
| modelSelection, | ||
| }); | ||
| } | ||
| yield* emitProviderEvent({ | ||
| type: "turn_item.updated", | ||
| driver: CLAUDE_PROVIDER, | ||
|
|
@@ -11904,6 +11924,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | |
| const applySelectionLive = Effect.fnUntraced(function* ( | ||
| live: ClaudeLiveQueryContext, | ||
| selection: CompiledClaudeModelSelection, | ||
| modelSelection: ModelSelection, | ||
| ) { | ||
| // Preserve launcher-selected models unless the model changed or a failed update left it unknown. | ||
| yield* ( | ||
|
|
@@ -11922,6 +11943,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | |
| ), | ||
| ); | ||
| live.selection = selection; | ||
| live.modelSelection = modelSelection; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }); | ||
|
|
||
| const openQuery = Effect.fnUntraced(function* ( | ||
|
|
@@ -11964,7 +11986,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | |
| (existing.selection.apiModelId === compiledSelection.apiModelId || | ||
| existing.openedPermissionMode === queryPolicy.permissionMode) | ||
| ) { | ||
| yield* applySelectionLive(existing, compiledSelection); | ||
| yield* applySelectionLive(existing, compiledSelection, turnInput.modelSelection); | ||
| } | ||
| if ( | ||
| existing !== null && | ||
|
|
@@ -11990,6 +12012,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | |
| }), | ||
| ); | ||
| } | ||
| existing.modelSelection = turnInput.modelSelection; | ||
| return existing; | ||
| } | ||
|
|
||
|
|
@@ -12130,6 +12153,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | |
| query: querySession, | ||
| queryPolicyKey, | ||
| selection: compiledSelection, | ||
| modelSelection: turnInput.modelSelection, | ||
| closed, | ||
| lastTurn: null, | ||
| promptEchoMode: "unknown", | ||
|
|
@@ -12844,8 +12868,8 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | |
| Effect.provideService(Path.Path, path), | ||
| ); | ||
| yield* backgroundForegroundSubagents(existing, backgroundDeadline); | ||
| if (liveSteerSelection !== undefined) { | ||
| yield* applySelectionLive(existing, liveSteerSelection); | ||
| if (liveSteerSelection !== undefined && turnInput.modelSelection !== undefined) { | ||
| yield* applySelectionLive(existing, liveSteerSelection, turnInput.modelSelection); | ||
| } | ||
| // The turn can end while the moves or the selection change are answered. | ||
| // A steer offered then would start a new turn the orchestrator never | ||
|
|
@@ -13245,7 +13269,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | |
| return; | ||
| } | ||
| // A failed update clears the live identity, so the next turn applies the selection again. | ||
| yield* applySelectionLive(live, selection).pipe( | ||
| yield* applySelectionLive(live, selection, modelSelection).pipe( | ||
| Effect.catch((cause) => | ||
| Effect.logWarning("orchestration-v2.claude-live-selection-failed", { cause }), | ||
| ), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
rg -n 'emitContextUsage|context\.input\.modelSelection|inherit' apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.tsRepository: pingdotgg/t3code
Length of output: 1758
Script executed:
Repository: pingdotgg/t3code
Length of output: 4997
Use the post-fallback selection for context-window and inherited-model values.
A session-scope
model_refusal_fallbackupdates onlyliveQuery.modelSelectionandliveQuery.selection.context.input.modelSelectionkeeps the turn-start model. The active turn then uses that old model in two places:emitContextUsagecallsclaudeContextWindow(context.input.modelSelection). If the fallback model has a different context window, the usage percentage and auto-compact threshold for the rest of the turn are computed from the wrong window size."inherit"branch readscontext.input.modelSelection.model. A subagent launched after the fallback can run on the pre-fallback model.Track the effective selection for the active turn, and read it in both places. Keep the model options that the fallback handler already preserves. The exact accessor for this selection is not established by the inspected code, so this comment does not include a patch.