Repository navigation
Preserve explicit Claude models across reconnects - #1214
Conversation
PR #1214: Preserve explicit Claude models across reconnects26 files, +1621 / -55 Scope🔴 PR has 1676 lines changed. Split into focused PRs. 🟡 1676 lines changed. PRs under 400 receive more thorough review. 🟡 26 files changed. Are all changes related? 🟡 Changes span 6 directories. StructuralOxlint structural signals:
Slop
Static AnalysisOxlint: 45 diagnostics across 4 files (10 rules) consistent-function-scoping (15): packages/acp/src/provider.ts CorrectnessFILE: packages/acp/src/provider.ts, PATTERN: no-shadow, CONCERN: Shadowing in loop variables, QUESTION: Are shadowed variables intentional or accidental? |
| cwd: sessionCwd, | ||
| mcpServers: this.options.mcpServers ?? [], | ||
| _meta: this.sessionMeta() | ||
| // XMD requested model: continuation metadata belongs to this call. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: continuation metadata belongs to this call. |
| cwd: sessionCwd, | ||
| mcpServers: this.options.mcpServers ?? [], | ||
| _meta: this.sessionMeta() | ||
| // XMD requested model: continuation metadata belongs to this call. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: continuation metadata belongs to this call. |
| } | ||
| ensureSession(input) { | ||
| this.assertOpen(); | ||
| // XMD requested model: shared sessions refuse this in-process input. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: shared sessions refuse this in-process input. |
| } | ||
| startTurn(input) { | ||
| this.assertOpen(); | ||
| // XMD requested model: shared sessions refuse this in-process input. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: shared sessions refuse this in-process input. |
| })); | ||
| } | ||
| async setConfigOption(input) { | ||
| // XMD requested model: shared sessions refuse this in-process input. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: shared sessions refuse this in-process input. |
| } | ||
| startTurn(input) { | ||
| const { handle, state } = this.resolveManagerHandle(input.handle); | ||
| // XMD requested model: validate before manager work begins. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: validate before manager work begins. |
| } | ||
| async *runTurn(input) { | ||
| const { handle, state } = this.resolveManagerHandle(input.handle); | ||
| // XMD requested model: validate before manager work begins. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: validate before manager work begins. |
| async setConfigOption(input) { | ||
| const { handle, state } = this.resolveManagerHandle(input.handle); | ||
| return await (await this.getManager()).setConfigOption(handle, input.key, input.value, state.mode, input); | ||
| // XMD requested model: validate before manager work begins. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: validate before manager work begins. |
| }; | ||
| } | ||
| async withRuntimeControlSession(record, sessionMode, run, replayOptions, authority) { | ||
| // XMD requested model: this control call supplies its own selection. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: this control call supplies its own selection. |
| this.drainPendingSessionUpdates(owner); | ||
| } | ||
| createTurnClient(record) { | ||
| // XMD requested model: a cold turn uses its own request. |
There was a problem hiding this comment.
Redundant comment — restates what the code does.
| // XMD requested model: a cold turn uses its own request. |
Why
A Claude Sidekick conversation with an explicit model could reconnect by selecting the global model and then selecting the conversation model again. Repeated turns accumulated avoidable model commands and configuration round trips. Fixes #1177.
What changes
An explicitly configured reconnect observes the exact restored model, preserves it during initialization, and skips saved-model replay when the exact supported ID already matches. A genuine selection change still applies and is verified before the next prompt.
The final trusted request exposes
requestedModel: string | undefined. This is the exact model requested for this use, rather than the provider's current or default model. Unconfigured and effort-only uses supply no requested model.How it works
Existing ensure, turn and configuration-write operations carry the exact requested model for that call. Cold clients check its presence and derive the existing Claude resume/load metadata marker. The string does not cross that adapter boundary, become durable state, or replace restored-model readback. Fresh creation and other providers receive no marker; shared-runtime use refuses a supplied model before effects.
The existing configuration path owns model application and verification. Ordinary and rollback writes carry the same request's model. Native launch, retained reacquisition, the headless host and loaded-copy bridges forward the exact value.
The Claude producer patch suppresses the global reset for a reconnect carrying the marker and requires a representable exact restored model. ACPX validates model support before skipping an equal resumed-model replay. Session ownership, release and retained live-client reuse keep their existing lifetimes.
Review guide
Start with
packages/acp/tests/claude-reconnect.test.ts, then Core's setup and structural bridge, provider/runtime forwarding and the readable Claude producer patch. Inspect adapter initialization and ACPX saved-model replay together.The ACP specification describes the requested-model contract. The latest follow-up replaces the earlier presence-only representation under Taras's explicit approval. It preserves the adapter protocol, producer recipe and model configuration owners.
What must stay true
Verification
Latest signed head:
c301818c6a74cf03f8d61ceae000b859522884b0, parent1ff203b261ca32b9041c8e6659729e39e66750bc.Current-head evidence:
deno task check, scoped lint/formatting and diff checks: PASS. Lint reports no errors.deno task verify:clean: PASS on the exact signed head, including clean preparation, offline host/release builds and three-runtime interference checks.The original Architect, Planner, Astra and required CI passes apply to
1ff203b2. Its retained whole affected selection passed 1,247 tests and 6,964 steps across 337 modules, in addition to 81 retained whole-module receipts. Its fresh Claude preparation reproduced the expected snapshot and passed negative controls. The producer inputs remain byte-identical in this follow-up. These prior receipts are not presented as reruns or verdicts on the successor. Required CI on the new head remains the delivery gate.Scope
Claude reconnect model-switch behavior and its evidence. Host-installed Codex execution was merged separately in #1204. Effort replay optimization is outside this change.
New abstractions
The provider-facing
requestedModelmember carries the existing request value through the established setup/runtime calls. Presence selects reconnect initialization behavior. It adds no author-facing option, model-setting path, desired-state cache or lifecycle owner.Generated or mechanical changes
The original readable Claude patch and pinned recipe produce the adapter snapshot. This follow-up updates the maintained ACPX runtime/declarations, manifest hashes, provenance and distinctive vendor locality marker. The producer patch, recipe and prepared snapshot are unchanged.
Risks and limitations
A reconnect carrying the marker stops before prompting when the restored model is missing or has no exact offered ID. Alias-only or hinted-model observations can therefore refuse; this strict-readback consequence was explicitly approved. No guessed value substitutes for an exact model ID.
The initial head's unchanged-revision retries and complete first outputs remain recorded under #1176, #1200 and #1215. Those flake issues remain open; this requested-model amendment claims no cause fix for them.
The #1203 guide pilot remains evidence for its original runtime and task history. This amendment does not replay that work, reset budgets, dispatch a benchmark or authorize a merge.
Scope confirmation