Summary
Every Cursor model returned by the live picker except composer-* and bare gemini ids fails immediately with:
Cursor agent stream error: {"code":"not_found","details":[{"debug":{"details":{"analyticsMetadata":{"actionRequired":"config"},"detail":"Unknown model ID: cursor-grok-4.6-high-fast",...},"error":"ERROR_BAD_MODEL_NAME","isExpected":true},...}]}
Reproduce: jcode provider-doctor cursor --tier full --model cursor-grok-4.6-high-fast (or pick any grok/claude/gpt model from /model and send a message).
Root cause
jcode-provider-cursor-runtime's hand-rolled protobuf/HTTP2 transport (agent_transport.rs, talking directly to agent.v1.AgentService/Run on agentn.global.api5.cursor.sh) sends the composite catalog id (e.g. cursor-grok-4.6-high-fast, claude-opus-5-thinking-high, gpt-5.6-sol-xhigh) verbatim as the model name.
The real backend — the same one the official cursor-agent CLI talks to with the same API key — only accepts the bare base id (grok-4.6, claude-opus-5, gpt-5.6-sol), with effort/fast/thinking sent as separate parameters. This is visible directly in ~/.cursor/cli-config.json's modelParameters, which is keyed by the bare id.
I confirmed this by running cursor-agent --model cursor-grok-4.6-high-fast -p "..." (the official CLI) with the exact same CURSOR_API_KEY jcode was using — it worked fine — while jcode's transport failed on the identical model id.
Fix
I already wrote, tested, and verified the fix locally. resolve_model_id() in agent_transport.rs strips, in this order, before building the wire request:
- a leading
cursor- vendor prefix
- a trailing
-fast suffix (captured separately as the fast flag)
- trailing effort/mode suffixes (
-xhigh, -high, -medium, -low, -thinking), which can stack (e.g. claude-opus-5-thinking-high → claude-opus-5)
Ids that don't match any pattern (composer-2.5, gemini-3.1-pro, sonnet-4.6) pass through unchanged.
Live verification (real Cursor account, real API key)
| model id from the live catalog |
before |
after |
cursor-grok-4.6-high-fast |
ERROR_BAD_MODEL_NAME |
chat reply OK |
claude-opus-5-thinking-high |
ERROR_BAD_MODEL_NAME |
chat reply OK |
claude-sonnet-5-thinking-high |
ERROR_BAD_MODEL_NAME |
chat reply OK |
gpt-5.4-high |
ERROR_BAD_MODEL_NAME |
chat reply OK |
gpt-5.6-sol-xhigh |
ERROR_BAD_MODEL_NAME |
chat reply OK |
gemini-3.7-flash-high |
ERROR_BAD_MODEL_NAME |
chat reply OK |
composer-2.5 (already bare) |
OK |
OK (unaffected) |
jcode provider-doctor cursor --tier full on cursor-grok-4.6-high-fast went from failing at "Non-streaming chat completion" to passing chat + streaming.
Tests
Added 6 unit tests (resolve_model_id_*) covering every composite pattern observed in the live catalog, rebased cleanly onto current master, and all 21 tests in jcode-provider-cursor-runtime pass.
Patch
I can't open a PR directly (repo's PR creation is restricted to collaborators), but the branch is public on my fork: https://github.com/matheusmaais/jcode/tree/fix/cursor-composite-model-ids (1 commit, rebased on current master, diff attached below for convenience).
Full diff
Summary
Every Cursor model returned by the live picker except
composer-*and bare gemini ids fails immediately with:Reproduce:
jcode provider-doctor cursor --tier full --model cursor-grok-4.6-high-fast(or pick any grok/claude/gpt model from/modeland send a message).Root cause
jcode-provider-cursor-runtime's hand-rolled protobuf/HTTP2 transport (agent_transport.rs, talking directly toagent.v1.AgentService/Runonagentn.global.api5.cursor.sh) sends the composite catalog id (e.g.cursor-grok-4.6-high-fast,claude-opus-5-thinking-high,gpt-5.6-sol-xhigh) verbatim as the model name.The real backend — the same one the official
cursor-agentCLI talks to with the same API key — only accepts the bare base id (grok-4.6,claude-opus-5,gpt-5.6-sol), witheffort/fast/thinkingsent as separate parameters. This is visible directly in~/.cursor/cli-config.json'smodelParameters, which is keyed by the bare id.I confirmed this by running
cursor-agent --model cursor-grok-4.6-high-fast -p "..."(the official CLI) with the exact sameCURSOR_API_KEYjcode was using — it worked fine — while jcode's transport failed on the identical model id.Fix
I already wrote, tested, and verified the fix locally.
resolve_model_id()inagent_transport.rsstrips, in this order, before building the wire request:cursor-vendor prefix-fastsuffix (captured separately as thefastflag)-xhigh,-high,-medium,-low,-thinking), which can stack (e.g.claude-opus-5-thinking-high→claude-opus-5)Ids that don't match any pattern (
composer-2.5,gemini-3.1-pro,sonnet-4.6) pass through unchanged.Live verification (real Cursor account, real API key)
cursor-grok-4.6-high-fastERROR_BAD_MODEL_NAMEclaude-opus-5-thinking-highERROR_BAD_MODEL_NAMEclaude-sonnet-5-thinking-highERROR_BAD_MODEL_NAMEgpt-5.4-highERROR_BAD_MODEL_NAMEgpt-5.6-sol-xhighERROR_BAD_MODEL_NAMEgemini-3.7-flash-highERROR_BAD_MODEL_NAMEcomposer-2.5(already bare)jcode provider-doctor cursor --tier fulloncursor-grok-4.6-high-fastwent from failing at "Non-streaming chat completion" to passing chat + streaming.Tests
Added 6 unit tests (
resolve_model_id_*) covering every composite pattern observed in the live catalog, rebased cleanly onto currentmaster, and all 21 tests injcode-provider-cursor-runtimepass.Patch
I can't open a PR directly (repo's PR creation is restricted to collaborators), but the branch is public on my fork: https://github.com/matheusmaais/jcode/tree/fix/cursor-composite-model-ids (1 commit, rebased on current master, diff attached below for convenience).
Full diff