Files
claude-agent-acp/src
Luca Dibattista ff9b96d462 fix: remove ~15s stall on session/new and model switch by seeding the context window synchronously (#894)
## Summary

`session/new` and switching models via `configOptions` regressed to ~15s
each in v0.59.0. This removes the blocking call responsible and seeds
the context window synchronously instead, restoring sub-second behavior
on both paths.

## Root cause

v0.59.0 added an inline `await query.getContextUsage()` on the
session-creation and model-switch paths. That SDK control request is not
serviced by the CLI until the session's first prompt turn has run —
before then it stalls ~15s. And because SDK control requests are
serialized over a single stdin/stdout channel, an awaited `setModel`
queued behind the wedged `getContextUsage` inherited the full stall. So
both paths blocked ~15s before returning.

Verified with the real CLI (setModel alone pre-prompt: ~1ms;
getContextUsage alone pre-prompt: ~16s; getContextUsage-then-setModel:
both resolve together at ~16s, confirming the serialization).

## Fix

Drop `getContextUsage` from both hot paths. The context window is seeded
synchronously by `immediateContextWindow(...)`:

1. an authoritative value from a per-(provider, model) cache, if
learned; else
2. a synchronous text heuristic (`\b1m\b` → 1,000,000); else
3. `DEFAULT_CONTEXT_WINDOW` (200,000).

The authoritative window is learned the moment it's actually available —
from each turn's `result.modelUsage` — and written into the cache, so
later sessions/switches to the same model seed the correct window
immediately with no IPC. A model that has never run a turn shows the
heuristic/default window until its first response, matching pre-0.59.0
behavior.

### Cache design

- **Keyed on `(provider, resolved model id)`.** The resolved id
(`ModelInfo.resolvedModel`, e.g. `claude-sonnet-5[1m]`) matches the
`result.modelUsage` keys, so the write key and the read keys line up.
Keying on the resolved id (not the picker value) dedups aliases that
resolve to the same model; keying on the provider keeps two providers
that reuse an id string for different windows from crossing wires
(per-session provider config, #853). The provider is captured at session
creation, since the effective provider can change process-wide over the
adapter's lifetime.
- **Non-positive/NaN windows are ignored** — they neither overwrite the
session's learned window nor enter the cache.

## Impact (measured, real CLI)

| Path | Before | After |
| --- | --- | --- |
| `session/new` | ~17.4s | ~0.9s |
| model switch (before first prompt) | ~15s | ~3ms |
| model switch (after a turn) | ~14.7s | <1ms |

## Tests

- New end-to-end cache test: a turn's authoritative window (learned
under the resolved id even when the assistant message reports the bare
id) is served on a later switch with no `getContextUsage`.
- New provider-isolation test: the same resolved id on a different
provider does not read another provider's cached window; the same
provider does.
- New non-positive-window test: a nonsensical `contextWindow` is ignored
and never poisons the cache.
- Rewrote the existing context-window tests to the new contract (assert
no `getContextUsage` on session/new and switch; window from
cache/inference/default).

Closes #886
Closes #880

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2026-07-20 12:48:40 +00:00
..
2026-06-25 21:42:58 +00:00