Summary
Follow-up to #799 (ACP migration + dual Model/Variant pickers). Operators still see a broken or confusing Cursor model picker in active remote sessions: the Variant column shows raw ACP wire suffixes (e.g. context=272k,reasoning=medium,fast=false, fast=true) instead of human-facing CLI SKU names (e.g. GPT-5.5 High Fast, Composer 2.5 Fast).
This is not a dual-picker UX bug. The picker logic in web/src/lib/cursorModelOptions.ts is correct when the catalog includes full cliModelSkus. The failure mode is incomplete SKU discovery/cache while an ACP session holds the Cursor CLI lock.
Root cause
Cursor model catalog uses two sources (#799 design):
| Source |
Example |
Role |
| ACP wires |
gpt-5.5[context=272k,reasoning=medium,fast=false] |
Authoritative for set_config_option |
| CLI SKUs |
gpt-5.5-high-fast |
Human-facing variant labels in picker |
While agent acp is running, HAPI must not spawn agent --list-models (it SIGTERM-kills the ACP child — see cli/src/agent/backends/acp/agentCliGuard.ts).
Observed failure chain:
- Session start seeds wires only —
syncCursorModelsFromAcp() in cli/src/cursor/cursorAcpRemoteLauncher.ts calls seedCursorModelsCache({ ...snapshot }) without cliModelSkus.
- Enrich early-return —
attachCliSkusToResponse / enrichCursorModelsWithCliSkus in cli/src/modules/common/cursorModels.ts historically returned immediately when any cliModelSkus were present, so a partial cache (e.g. 2 SKUs) could never be merged with the full list (~106 SKUs on a typical host).
- Web prefers partial session SKUs —
SessionChat used session.cliModelSkus whenever length > 0, ignoring the richer machine RPC response from the runner process (which usually has no ACP lock and can cold-probe).
When cliModelSkus are missing/incomplete, resolveCursorVariantOptions falls back to raw ACP wire rows and the picker looks "怪".
Repro (local)
- Machine with Cursor
agent on PATH; cold listCursorModels returns ~28 ACP wires + ~106 CLI SKUs.
- Start a Cursor remote ACP session (session child holds ACP lock).
- Open in-session Model/Variant picker before runner shared cache is fully populated, or with a stale
HAPI_HOME/cache/cursor-models.json containing only a few cliModelSkus.
- Expected: Variant rows like
GPT-5.5 High Fast, Composer 2.5 Fast.
- Actual: Variant rows like
context=272k,reasoning=medium,fast=false or only 2 variants for gpt-5.5.
Empirical baseline: with full SKU merge, composer-2.5 shows Composer 2.5 | Composer 2.5 Fast; gpt-5.5 shows 10 CLI variants.
Proposed fix
CLI (cache correctness)
attachCliSkusToResponse / enrichCursorModelsWithCliSkus: merge SKU lists; never early-return on partial cliModelSkus.
syncCursorModelsFromAcp: when seeding wires, inherit cliModelSkus from readSharedCursorModelsCache() so session RPC under lock still returns full SKUs after a runner cold probe.
listCursorModelsWhileAcpActive: union in-memory + shared cliModelSkus before attach.
Web (defense in depth)
mergeCursorCliModelSkus(machine, session) — prefer richer source (machine RPC often has full list).
- Extract
buildInSessionCursorCatalogSources() so merge logic is not scattered in SessionChat.tsx.
Optional
- Runner background pre-warm:
listCursorModels() on runner start to populate cursor-models.json before first in-session picker open.
Scope / files
| Area |
Path |
| Session seed |
cli/src/cursor/cursorAcpRemoteLauncher.ts |
| Catalog RPC |
cli/src/modules/common/cursorModels.ts |
| Shared cache |
cli/src/modules/common/cursorModelsSharedCache.ts |
| Web catalog |
web/src/lib/cursorPickerState.ts, web/src/components/SessionChat.tsx |
Acceptance criteria
- In-session Cursor picker shows CLI SKU names for variants when
agent --list-models lists matching SKUs (not raw ACP param strings).
- When session RPC returns 2
cliModelSkus and machine RPC returns 106, the UI uses the union (106).
- ACP-active code paths never spawn
agent --list-models.
- Regression tests for partial-cache merge + session seed inheriting shared SKUs.
Non-goals
Related
Summary
Follow-up to #799 (ACP migration + dual Model/Variant pickers). Operators still see a broken or confusing Cursor model picker in active remote sessions: the Variant column shows raw ACP wire suffixes (e.g.
context=272k,reasoning=medium,fast=false,fast=true) instead of human-facing CLI SKU names (e.g.GPT-5.5 High Fast,Composer 2.5 Fast).This is not a dual-picker UX bug. The picker logic in
web/src/lib/cursorModelOptions.tsis correct when the catalog includes fullcliModelSkus. The failure mode is incomplete SKU discovery/cache while an ACP session holds the Cursor CLI lock.Root cause
Cursor model catalog uses two sources (#799 design):
gpt-5.5[context=272k,reasoning=medium,fast=false]set_config_optiongpt-5.5-high-fastWhile
agent acpis running, HAPI must not spawnagent --list-models(it SIGTERM-kills the ACP child — seecli/src/agent/backends/acp/agentCliGuard.ts).Observed failure chain:
syncCursorModelsFromAcp()incli/src/cursor/cursorAcpRemoteLauncher.tscallsseedCursorModelsCache({ ...snapshot })withoutcliModelSkus.attachCliSkusToResponse/enrichCursorModelsWithCliSkusincli/src/modules/common/cursorModels.tshistorically returned immediately when anycliModelSkuswere present, so a partial cache (e.g. 2 SKUs) could never be merged with the full list (~106 SKUs on a typical host).SessionChatusedsession.cliModelSkuswheneverlength > 0, ignoring the richermachineRPC response from the runner process (which usually has no ACP lock and can cold-probe).When
cliModelSkusare missing/incomplete,resolveCursorVariantOptionsfalls back to raw ACP wire rows and the picker looks "怪".Repro (local)
agenton PATH; coldlistCursorModelsreturns ~28 ACP wires + ~106 CLI SKUs.HAPI_HOME/cache/cursor-models.jsoncontaining only a fewcliModelSkus.GPT-5.5 High Fast,Composer 2.5 Fast.context=272k,reasoning=medium,fast=falseor only 2 variants forgpt-5.5.Empirical baseline: with full SKU merge,
composer-2.5showsComposer 2.5 | Composer 2.5 Fast;gpt-5.5shows 10 CLI variants.Proposed fix
CLI (cache correctness)
attachCliSkusToResponse/enrichCursorModelsWithCliSkus: merge SKU lists; never early-return on partialcliModelSkus.syncCursorModelsFromAcp: when seeding wires, inheritcliModelSkusfromreadSharedCursorModelsCache()so session RPC under lock still returns full SKUs after a runner cold probe.listCursorModelsWhileAcpActive: union in-memory + sharedcliModelSkusbefore attach.Web (defense in depth)
mergeCursorCliModelSkus(machine, session)— prefer richer source (machine RPC often has full list).buildInSessionCursorCatalogSources()so merge logic is not scattered inSessionChat.tsx.Optional
listCursorModels()on runner start to populatecursor-models.jsonbefore first in-session picker open.Scope / files
cli/src/cursor/cursorAcpRemoteLauncher.tscli/src/modules/common/cursorModels.tscli/src/modules/common/cursorModelsSharedCache.tsweb/src/lib/cursorPickerState.ts,web/src/components/SessionChat.tsxAcceptance criteria
agent --list-modelslists matching SKUs (not raw ACP param strings).cliModelSkusand machine RPC returns 106, the UI uses the union (106).agent --list-models.Non-goals
claude-sonnet-4wires) — feat(cursor): migrate remote sessions to ACP with model/variant pickers #799 explicitly avoids semantic guessing on ACP param strings.Related
agent -pstream-json mode to Cursor ACP mode #781 — ACP migration umbrella (closed)