@cursor/sdk local agent: named models fail with status: "error" and no error message

Hi Cursor team,

I’m building a headless app using @cursor/sdk (local runtime). Cursor.models.list() returns the full catalog (~30 models) with parameters and variants, but any explicitly named model fails at run time. Only default / auto completes successfully.

This happens with BYOK disabled and no custom API keys on the account, just a standard Cursor user API key from the dashboard.

SDK @cursor/[email protected]
BYOK Disabled
Runtime Local (local: { cwd, settingSources: })
Custom provider keys None on account
Auth User API key (CURSOR_API_KEY from Cursor Dashboard → API Keys)
Node v22.19.0

Minimal repro (no MCP, no custom tools, no project settings):

import { Agent, Cursor } from "@cursor/sdk";

const apiKey = process.env.CURSOR_API_KEY!;

// Step 1: catalog works — returns 30 models including composer-2.5, claude-sonnet-4-6, etc.
const models = await Cursor.models.list({ apiKey });
const composer = models.find((m) => m.id === "composer-2.5");
// composer.parameters includes { id: "fast", values: ["false", "true"] }
// composer.variants includes isDefault: true with params: [{ id: "fast", value: "true" }]

// Step 2: named model fails
const agent = await Agent.create({
  apiKey,
  model: {
    id: "composer-2.5",
    params: [{ id: "fast", value: "true" }], // catalog default variant
  },
  local: { cwd: process.cwd(), settingSources: [] },
});

const run = await agent.send("Reply with exactly: ok");
const result = await run.wait();
// result.status === "error"

Same failure with:

  • { id: "composer-2.5" } (no params)
  • Default variant params from variants.find(v => v.isDefault)
  • Other models: claude-sonnet-4-6, gpt-5.4-mini, claude-opus-4-6
  • Both minimal config (settingSources: []) and full bot config (settingSources: ["project"], MCP, custom tools)

What works:

model: { id: "default" }  // ✅ status: "finished"
model: { id: "auto" }     // ✅ resolves to default, status: "finished"


What happens

  1. Agent.create() succeeds — agent ID is assigned (e.g. agent-80704988-...).

  2. agent.send() succeeds — run ID and requestId are returned.

  3. Stream emits:

    {"type":"status","status":"RUNNING"}
    {"type":"status","status":"ERROR"}
    
    
  4. run.wait() resolves with:

    {
      "status": "error",
      "model": { "id": "composer-2.5", "params": [{ "id": "fast", "value": "true" }] },
      "durationMs": 3076,
      "result": undefined
    }
    
    
  5. run.conversation() returns [] (empty — no assistant text, no error detail).

  6. No exception is thrown — this is a run-level failure, not a CursorAgentError startup failure.


Error codes / identifiers

There is no HTTP status code or error message in the response. What we do get:

Field Example value
run.status "error"
Stream status "ERROR" (after "RUNNING")
run.requestId f6ff0b72-9585-4278-9aa8-31b237362eb0
run.id run-b397e483-32eb-4c57-935a-c3a82282f107
agent.agentId agent-80704988-2991-41cd-957e-df710d01fa79
result.result undefined
run.conversation() []

No CursorAgentError is thrown. No message like “model not available” or “unsupported model” appears in the stream or RunResult.


What I’ve ruled out

  • Missing model.params — docs say parameterized models need explicit params; I pass the catalog default variant (fast: true for composer-2.5). Still fails.
  • BYOK / custom keys — disabled and confirmed none on account.
  • Wrong model ID — IDs match Cursor.models.list() exactly; aliases also tried.
  • Local config pollution — fails with settingSources: [] (no project/user settings loaded).
  • SDK version — reproduced on @cursor/[email protected].

Has anyone else experienced this? If so, were you able to identify the cause or find a workaround?

Thanks!

The missing error message is probably the biggest issue here.

If the catalog returns those named models, the SDK user will assume they are valid inputs. When the run only returns status error, it becomes hard to tell whether the problem is auth, model routing, params, local runtime, or account eligibility.

Hey, thanks for the detailed report. The request ID and steps really helped speed up the investigation.

Main point: this isn’t a bug in specific models. From your request ID, we can see the named models are being rejected with HTTP 429 usage limit, not with a model unavailable type error. When your account goes over the included usage limit, Cursor limits agent requests to Auto default only. That’s why default auto works, but any explicitly named model composer-2.5, claude-, gpt- returns 429.

The part where you get a vague status error with an empty conversation is a known gap in the SDK. run.wait currently doesn’t surface stream errors, and RunResult doesn’t have an error field, so the 429 never reaches you. We’ve logged an issue for this, but I can’t share an ETA yet. And yes, Jason_Luo1 is right, the swallowed error message is the biggest pain here.

What you can do right now:

  • Use default auto, it will resolve to an allowed model
  • Wait for the included usage window to reset, you can check status in the dashboard
  • Turn on usage-based pricing or upgrade for more included usage

Named models will work again as soon as you’re back under the limit. Let me know if it still happens after the limit resets, and we’ll dig deeper.

I met the same problem, cursor sdk should have more useful response message to help us solve the problem.

Hey @JeremyChow, thanks for following up. Just seeing status: "error" with no details is a known gap in the SDK. run.wait() doesn’t currently surface the stream error, so the real reason never reaches you. We’re working on better error messaging, but I can’t share an ETA yet.

That said, a bare error almost always hides a specific server-side reason. For the thread author, it turned out to be an HTTP 429 usage limit. Once you exceed included usage, agent requests are limited to Auto default only, so any named model fails.

To figure out if that’s what’s happening for you or if it’s something else like auth, params, or environment, can you share:

  • which SDK and version you’re using TS @cursor/sdk or Python cursor-sdk
  • the Request ID from the failed run run.requestId
  • whether model: { id: "default" } or "auto" works for you

With the Request ID, we can check exactly why the server rejected the request and give you a more precise answer.