Skip to content

fix(cursor): incomplete cliModelSkus during ACP lock — variant picker shows raw wire params #831

Description

@swear01

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:

  1. Session start seeds wires onlysyncCursorModelsFromAcp() in cli/src/cursor/cursorAcpRemoteLauncher.ts calls seedCursorModelsCache({ ...snapshot }) without cliModelSkus.
  2. Enrich early-returnattachCliSkusToResponse / 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).
  3. Web prefers partial session SKUsSessionChat 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)

  1. Machine with Cursor agent on PATH; cold listCursorModels returns ~28 ACP wires + ~106 CLI SKUs.
  2. Start a Cursor remote ACP session (session child holds ACP lock).
  3. 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.
  4. Expected: Variant rows like GPT-5.5 High Fast, Composer 2.5 Fast.
  5. 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

  1. In-session Cursor picker shows CLI SKU names for variants when agent --list-models lists matching SKUs (not raw ACP param strings).
  2. When session RPC returns 2 cliModelSkus and machine RPC returns 106, the UI uses the union (106).
  3. ACP-active code paths never spawn agent --list-models.
  4. Regression tests for partial-cache merge + session seed inheriting shared SKUs.

Non-goals

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions