Skip to content

Preserve explicit Claude models across reconnects - #1214

Merged
taras merged 2 commits into
mainfrom
codex/claude-reconnect-1177
Nov 10, 2026
Merged

taras merged 2 commits into
mainfrom
codex/claude-reconnect-1177

Conversation

@taras

@taras taras commented Nov 9, 2026 •

Copy link
Copy Markdown
Owner

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.

final request -> requestedModel -> current operation -> Claude resume/load marker
                                                   -> exact model readback
                                                   -> configure and verify -> prompt

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

  • Repeated cold reconnects use the exact selected model without avoidable global or replay model writes; tests inspect both commands and actual prompt models.
  • Genuine changes, native handoff, external model drift and reacquisition still configure and verify the exact requested model before inference.
  • Fresh, unconfigured and effort-only behavior, other-provider isolation, malformed input refusal and shared-runtime refusal remain covered.
  • Configuration verification, conversation identity, release, rollback and fencing retain their existing owners.

Verification

Latest signed head: c301818c6a74cf03f8d61ceae000b859522884b0, parent 1ff203b261ca32b9041c8e6659729e39e66750bc.

Current-head evidence:

  • Focused Core setup/session-use, structural bridge, provider, native launch, loaded-copy and Claude reconnect tests: PASS, 36 groups and 267 steps.
  • Core/vendor checks: PASS, 4 groups and 33 steps, including exact manifest hashes and unchanged vendor locality limits.
  • Claude reconnect suite under Node and Bun: PASS, 9 tests each. Deno passes the same suite in the focused matrix.
  • Protocol/materialization and distribution/built-npm-package tests: PASS, 7 groups and 73 steps.
  • Repository-wide deno task check, scoped lint/formatting and diff checks: PASS. Lint reports no errors.
  • JSR dry run, build and compiled Claude smoke: PASS. All 29 runtime executables match, and the original SDK is absent.
  • deno task verify:clean: PASS on the exact signed head, including clean preparation, offline host/release builds and three-runtime interference checks.
  • Independent Codex Astra review: PASS on this exact head, using the existing approved fallback after the preserved Fable quota refusal.

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 requestedModel member 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.

  • The member has concrete consumers and a bounded initialization purpose.
  • No speculative functionality is included.

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

  • Every changed file supports the reconnect repair or its evidence.
  • Unrelated cleanup and formatting changes are excluded.
  • Maintained snapshot changes identify their source and provenance.
  • Verification and review evidence are qualified by exact revision.

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

PR #1214: Preserve explicit Claude models across reconnects

26 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.

Structural

Oxlint structural signals:

  • no-unused-vars ×4: packages/core/src/agent/provider-api.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts
  • no-unnecessary-type-assertion ×1: packages/acp/tests/helpers.ts

Slop

  • packages/acp/vendor/acpx/generated/queue-owner-runtime-B0mPqOu4.js:3130 — // XMD requested model: continuation metadata belongs to this call.
  • packages/acp/vendor/acpx/generated/queue-owner-runtime-B0mPqOu4.js:3150 — // XMD requested model: continuation metadata belongs to this call.
  • packages/acp/vendor/acpx/generated/runtime.js:2623 — // XMD requested model: shared sessions refuse this in-process input.
  • packages/acp/vendor/acpx/generated/runtime.js:2642 — // XMD requested model: shared sessions refuse this in-process input.
  • packages/acp/vendor/acpx/generated/runtime.js:2791 — // XMD requested model: shared sessions refuse this in-process input.
  • packages/acp/vendor/acpx/generated/runtime.js:1997 — // XMD requested model: consumed only by this attaching client.
  • packages/acp/vendor/acpx/generated/runtime.js:2904 — // XMD requested model: consumed only by this attaching client.
  • packages/acp/vendor/acpx/generated/runtime.js:2949 — // XMD requested model: consumed only by this attaching client.
  • packages/acp/vendor/acpx/generated/runtime.js:2991 — // XMD requested model: consumed only by this attaching client.
  • packages/acp/vendor/acpx/generated/runtime.js:2894 — // XMD requested model: validate before manager work begins.
  • packages/acp/vendor/acpx/generated/runtime.js:2935 — // XMD requested model: validate before manager work begins.
  • packages/acp/vendor/acpx/generated/runtime.js:2977 — // XMD requested model: validate before manager work begins.
  • packages/acp/vendor/acpx/generated/runtime.js:3014 — // XMD requested model: validate before manager work begins.
  • packages/acp/vendor/acpx/generated/runtime.js:1390 — // XMD requested model: this control call supplies its own selection.
  • packages/acp/vendor/acpx/generated/runtime.js:1974 — // XMD requested model: a cold turn uses its own request.

Static Analysis

Oxlint: 45 diagnostics across 4 files (10 rules)
Density: 0.028 violations/added-line

consistent-function-scoping (15): packages/acp/src/provider.ts
no-shadow (9): packages/cli/src/headless/host.ts, packages/acp/src/provider.ts
no-useless-spread (6): packages/cli/src/headless/host.ts, packages/acp/tests/helpers.ts, packages/acp/src/provider.ts
no-unused-vars (4): packages/core/src/agent/provider-api.ts, packages/acp/src/provider.ts
unbound-method (4): packages/acp/tests/helpers.ts, packages/acp/src/provider.ts, packages/cli/src/headless/host.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unnecessary-type-assertion (1): packages/acp/tests/helpers.ts
no-unsafe-type-assertion (1): packages/acp/tests/helpers.ts
no-unnecessary-template-expression (1): packages/acp/src/provider.ts

Correctness

FILE: packages/acp/src/provider.ts, PATTERN: no-shadow, CONCERN: Shadowing in loop variables, QUESTION: Are shadowed variables intentional or accidental?
FILE: packages/acp/src/provider.ts, PATTERN: no-unused-vars, CONCERN: Unused variables in modelRequest, QUESTION: Are these variables required for future extensibility or redundant?
FILE: packages/acp/tests/helpers.ts, PATTERN: no-shadow, CONCERN: Shadowing in test helper loops, QUESTION: Intended behavior or accidental duplication?
FILE: packages/acp/tests/helpers.ts, PATTERN: no-unused-vars, CONCERN: Unused test utility functions, QUESTION: Are these functions obsolete or misplaced?
FILE: packages/cli/src/headless/host.ts, PATTERN: no-shadow, CONCERN: Variable shadowing in host setup, QUESTION: Intended scoping or accidental?
FILE: packages/acp/src/provider.ts, PATTERN: no-empty-function, CONCERN: Empty functions in provider logic, QUESTION: Placeholder code or incomplete implementation?
FILE: packages/acp/src/provider.ts, PATTERN: no-floating-promises, CONCERN: Unhandled promises in model handling, QUESTION: Intended async flow or oversight?
FILE: packages/acp/src/provider.ts, PATTERN: no-unnecessary-type-assertion, CONCERN: Redundant type assertions in config handling, QUESTION: Simplify type inference or necessary?
FILE: packages/acp/src/provider.ts, PATTERN: no-unsafe-type-assertion, CONCERN: Unsafe type assertions in model resolution, QUESTION: Risk of runtime errors from incorrect types?
FILE: packages/acp/tests/helpers.ts, PATTERN: no-unnecessary-template-expression, CONCERN: Superfluous template literals in test setup, QUESTION: Simplify string construction?

@taras
taras marked this pull request as ready for review November 9, 2026 16:06

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 15 redundant comments. Inline suggestions to remove them below.

cwd: sessionCwd,
mcpServers: this.options.mcpServers ?? [],
_meta: this.sessionMeta()
// XMD requested model: continuation metadata belongs to this call.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// XMD requested model: continuation metadata belongs to this call.

}
ensureSession(input) {
this.assertOpen();
// XMD requested model: shared sessions refuse this in-process input.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// XMD requested model: shared sessions refuse this in-process input.

}
startTurn(input) {
this.assertOpen();
// XMD requested model: shared sessions refuse this in-process input.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// XMD requested model: shared sessions refuse this in-process input.

}));
}
async setConfigOption(input) {
// XMD requested model: shared sessions refuse this in-process input.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// XMD requested model: a cold turn uses its own request.

@taras
taras merged commit 220e54f into main Nov 10, 2026
64 checks passed
@taras
taras deleted the codex/claude-reconnect-1177 branch November 10, 2026 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Keep Sidekick’s selected Claude model across reconnects

1 participant