Appearance
Fix common agent problems
Start with three checks:
- Run
agent-sdk validate --dir .. - Confirm the expected agent and connections in
agent-sdk info --dir . --json. - Reproduce the problem once, then read the serve log or the session trace.
Serve or the playground won't work
| What you see | What to do |
|---|---|
| The playground is blank or lists no agents | Start agent-sdk serve, because the playground gets its agents from the server. |
| Agent-file edits don't appear | Open the URL that serve --dev prints, and press Enter in its terminal to reload. |
| Sessions exist but the list is empty | The list shows only your own sessions, so use the identity that created them. |
A turn fails or misbehaves
| What you see | What to do |
|---|---|
Reads and greps keep failing with NGHTTP2_FRAME_SIZE_ERROR | Run the agent under Node 22.13 or newer, never Bun. |
Semantic search has been removed. Use Grep. | Delete search from a custom fs, drop SearchQuery and SearchResult, and remove SemanticSearch or semSearch from tools and bot/tools/. |
| The turn reports a missing API key | Set CURSOR_API_KEY, CURSOR_API_KEY_FILE, or CURSOR_SERVICE_ACCOUNT_KEY, or run agent-sdk login. |
| Login works but the model host rejects the key | Point CURSOR_API_BASE_URL and CURSOR_BACKEND_URL at the same API host. |
The first turn reports [resource_exhausted] | The model is usually out of capacity. If a GitHub event started the turn, check for a GitHub 429 rate-limit response first. |
Replies quote an ancestor AGENTS.md or unrelated repository rules | Set defineAgent({ local: { cwd } }) so the agent doesn't inherit the parent checkout. /v1/info shows the current local.cwd. |
| The model lists IDE meta-tools but never calls named MCP tools | Set advertiseTools: true on the local connection. |
| Server tools, skills, or seed files are missing | Check the discovered project in /v1/info, and follow the warning from validate. |
validate and run pass but CI type-checking fails | Return JSON-shaped tool results, such as object literals or type aliases, instead of interface types. |
HTTP returns 401, 403, or 409
| What you see | What to do |
|---|---|
401 outside local development | Send the bearer token, alias token, or channel credential. Default custom routes accept only direct loopback calls. |
403 on a stream or follow-up | Use the identity that created the session. |
| A custom route fails through a tunnel or LAN | Protect the host with --bearer-token or channel auth. Use --allow-anonymous only behind a proxy that authenticates every caller. |
409 on a follow-up | Refresh the rotating continuationToken. Task sessions don't accept follow-ups. |
409 session_busy from call --session | Wait for the turn, omit --session, or mark a read-only tool effect: "read" so it can run alongside the turn. |
A hosted deployment fails or looks stale
| What you see | What to do |
|---|---|
validate or deploy reports architecture "v2" requires a named secret | Replace the literal or getter with a named secret, and set its value with agent-sdk secret set <slug> NAME. |
| Local changes don't appear after deploy | deploy builds the pushed ref, so commit and push your changes, then deploy again. |
The deployment never reaches running | Run agent-sdk deployment get <slug>, and fix the error it reports. |
| A secret or egress change has no effect | Confirm the name with secret list, check that the hosting block is on the pushed ref, and redeploy. |
| A hosted schedule doesn't fire | Check that the deployment is running and that the schedule is in bot/schedules/ on the deployed ref. |
A custom route returns 429 | Wait for the time in Retry-After, then send it again. |
GitHub deliveries fail
| What you see | What to do |
|---|---|
github forward gets 401 on every delivery | Clear the token variables so the forwarder uses your gh login, as in GITHUB_TOKEN= GH_TOKEN= agent-sdk github forward. |
Hook already exists | Stop the stale forwarder. GitHub allows one per repository. |
Direct webhooks are rejected outside --dev | Set the same GITHUB_WEBHOOK_SECRET on the signer and the server. |
| You can't create a forwarder | Use agent-sdk github replay <pr-url>, which needs only pull access. |
Slack doesn't respond
| What you see | What to do |
|---|---|
Logs say channel idle ... missing credentials | Run agent-sdk slack create --dir <agent>, then check the prefix with agent-sdk slack doctor. |
slack create waits for workspace approval | Keep the CLI running and open the Request approval link it prints. After an admin approves, select Retry in the Deployed Agents Add Slack wizard. |
| The bot ignores ordinary channel posts | Configure engagement.channelPosts, create the app with --channel-posts, and invite it to each allowed channel. |
MCP authorization fails
| What you see | What to do |
|---|---|
must be defineConnection({ url, oauth: true }) | Add oauth: true to the connection, or fix the filename you passed to mcp oauth. |
| Local auth works but hosted calls return unauthorized | Your local OAuth tokens don't reach a deployment. Use a cursorAccount: true connection, as described in Host MCP OAuth. |
The model invents mcp_auth instead of calling the server | Set advertiseTools: true for named local tools, or expose a wrapper through ctx.host.mcp. |
Schedules or reminders stall
| What you see | What to do |
|---|---|
A schedule or reminder never fires under --dev | --dev doesn't fire them on its own, so trigger one through its /v1/dev/schedules/<id> or /v1/dev/reminders/<id> route. |
A reminder is disarmed with handler_lost_on_restart | Recreate handler-form reminders after a restart, or use a prompt-form reminder. |
| One schedule fires on several self-hosted processes | Pass --no-schedules to every serving process but one. |
Read the session trace
bash
agent-sdk session list --prod --slug <slug>
agent-sdk session get <sessionId> --prod --slug <slug> --view events
agent-sdk session get --file <trace-path>Find a hosted session with session list, then print its events with --view events. For a local run, pass the trace path from agent-sdk run to --file. Pair each actions.requested event with its action.result to find the call that failed.