Appearance
Fix common agent problems
Start with four checks, in order:
- Project discovery:
agent-sdk validate --dir . - Whether the serve process is running
- What the playground or HTTP API shows
- The session event stream (trace)
Match your symptom below. Keep the commands as agent-sdk; see Run the CLI if you still need an alias.
What if serve or the playground looks wrong?
| What you see | What to do |
|---|---|
serve won't start | Run agent-sdk validate --dir . and fix the reported errors. |
| Playground is blank or says there are no agents | The UI needs a running serve process. Building the playground assets alone is not enough. |
| Edits to the playground don't show up | Use serve --dev and open the printed playground HMR URL (often port 5273), not only the static :3000 URL. |
| Sessions exist on disk but the playground list is empty | The list shows sessions for the authenticated caller. In --dev on loopback the list is wider. Otherwise open /<slug>/playground?sessionId=ses_… or inspect sessions/ under your state root. |
| Port 3000 or 5273 is already in use | For the default serve port, the CLI tries the next free port and prints a notice. Pass --port to pick one, or --port 0 for any free port. Stop leftover Vite or webhook-forwarder processes if you need the original port. |
What if a model turn goes wrong?
| What you see | What to do |
|---|---|
| Built-in file reads and greps fail; the turn retries for a long time | Run under Node 22.13+ (or tsx), never Bun. Look for NGHTTP2_FRAME_SIZE_ERROR in logs. |
| The turn fails immediately with an API-key error | Sign in with agent-sdk login, or set CURSOR_API_KEY. Discovery, info, call, and serve bring-up work without a key; model turns need one. |
Replies quote rules or AGENTS.md from outside your agent project | The session workspace inherited parent-folder config. Point defineAgent({ local: { cwd } }) outside that tree, or set --state-root to a clean directory (for example under /tmp). |
| Server tools, skills, or workspace seed files never appear | The agent runtime is cloud. Those features apply on the local runtime. validate warns when this combination is present. |
validate and run succeed, but typecheck fails in CI | The CLI runs TypeScript with type-stripping only. Keep tool execute return types as object literals or type aliases, not interface types. |
| Login works, but turns are rejected when using custom API hosts | Point login and model traffic at the same host (CURSOR_API_BASE_URL and CURSOR_BACKEND_URL). A key from one host is rejected by the other. |
What if the HTTP API returns an error?
| What you see | What to do |
|---|---|
409 on a follow-up message | Refresh the continuationToken (it rotates on each accepted follow-up), wait if the session is busy, or confirm the session is a chat session (task and schedule sessions are not followable). |
409 session_busy on call --session | Wait for the model turn to finish, or omit --session for a one-off call. |
403 on stream or follow-up | Use the same auth identity that created the session. Off localhost, pass --bearer-token and send it on every request. |
| Works on localhost; blocked through a tunnel or LAN | Default auth allows only direct loopback callers. Share the host with --bearer-token <secret> (or authored bearerAuth). Use --allow-anonymous only on a trusted private network. |
| A channel route fails to compile with a schema type error | GET routes need a Zod querySchema. POST / PUT / PATCH need a Zod bodySchema. Use z.object({}) or z.unknown() for open shapes. |
What if GitHub webhooks misbehave?
| What you see | What to do |
|---|---|
github forward returns 401 on every delivery, but the hook was created | Clear GITHUB_TOKEN and GH_TOKEN for that command. The forwarder uses your gh CLI login: GITHUB_TOKEN= GH_TOKEN= agent-sdk github forward … |
Hook already exists when starting a forwarder | GitHub allows one forwarder per repo. Run a single github forward --dir <parent> and stop stale forwarders. |
Deliveries rejected outside --dev | Set GITHUB_WEBHOOK_SECRET on the server and on the signer. Without a secret, the channel stays loopback-only. |
| You lack repo admin and can't forward | Use agent-sdk github replay <pr-url>. It needs pull access only and posts signed test payloads. |
What if Slack stays quiet?
| What you see | What to do |
|---|---|
Logs show channel idle … missing credentials | Expected when tokens are missing. Run agent-sdk slack create --dir <agent> to provision the app and write the tokens, or agent-sdk slack init --manual --dir <agent> and paste the manifests at api.slack.com. Then set <PREFIX>_SLACK_BOT_TOKEN and <PREFIX>_SLACK_APP_TOKEN per agent and run agent-sdk slack doctor --prefix <PREFIX>. |
slack create reports the app needs admin approval | Open Slack's Request approval page (the CLI prints the link; the same URL is Send a reminder after you submit). Managed install does not file the request. Keep the CLI running, then click Retry in the dashboard after an admin approves. |
| The bot ignores ordinary channel posts | Default engagement is mentions and DMs only. Enable engagement.channelPosts with an allowlist, and subscribe the app to message.channels / message.groups. |
| Approve / Deny buttons do nothing | Channels that post approval cards need toolApprovals: true. Recreate the app with slack create if interactivity is off. |
What if host MCP OAuth fails?
| What you see | What to do |
|---|---|
must be defineConnection({ url, oauth: true }) | The connection file needs oauth: true, or you passed the wrong connection name to agent-sdk mcp oauth. |
| Local auth works; hosted calls unauthorized | Run agent-sdk mcp oauth <name> --store, confirm names with agent-sdk secrets list <slug>, then redeploy. |
Model asks for mcp_auth or IDE MCP for a privileged server | That connection is likely hostOnly. Call it from a host tool via ctx.host.mcp, and update instructions. |
See Host MCP OAuth and skills/mcp-auth/SKILL.md.
What if a secret showed up in a terminal transcript?
| What you see | What to do |
|---|---|
secrets set … NAME=VALUE in an agent-captured terminal or shell history | Rotate the secret at the provider. Set it again with names only: agent-sdk secrets set <slug> NAME (hidden prompt) or pipe/redirect the value. NAME=VALUE requires --from-argv and still leaks into argv. |
Alias token printed during first deploy or rotate-token | Treat it as exposed if the transcript left your machine. Run agent-sdk rotate-token <slug>, store the new token outside agent transcripts, and update callers. |
Someone verified a secret with echo / printenv | Rotate it. Confirm presence with agent-sdk secrets list <slug> (names only), then redeploy and test the feature. |
What if schedules, reminders, or approvals stall?
| What you see | What to do |
|---|---|
A schedule or reminder never fires under --dev | Dev mode does not auto-fire. Trigger with POST /<slug>/v1/dev/schedules/<id> or POST /<slug>/v1/dev/reminders/<id> (list reminders at GET /v1/dev/reminders). |
| A pending tool approval disappeared after restart | Parked approvals do not survive host restart. They resolve as interrupted. Run the turn again. |
A reminder is disarmed after restart (handler_lost_on_restart) | Handler-form reminders live in memory. Re-arm them from the code that created them, or use prompt-form reminders. |
How do I read a session trace?
Look at actions.requested / action.result pairs for the tool trajectory. Count calls by tool name before blaming latency. Separate host-side work (channel callTool, preparation) from tools the model chose.
turn.failed with "turn interrupted" means a follow-up or stop ended the turn on purpose.
If the model reads outside the session workspace, the prepared files don't match what the instructions expect. Fix the layout. See Hillclimbing.
agent-sdk trajectory --events <file> summarizes any saved NDJSON stream. The playground Open trace control does the same visually.
What's next
- Concepts: the model behind these symptoms
- Hillclimbing: when the agent runs but underperforms
- Deployment: auth and state on shared hosts