Cursor SDK 错误处理实战指南:区分“启动失败“与“运行失败“的两类故障模型
发布时间:2026/9/16 10:56:14 锦皓数字建站

Cursor SDK 错误处理实战指南区分启动失败与运行失败的两类故障模型【免费下载链接】pluginsCursor plugin specification and official plugins项目地址: https://gitcode.com/GitHub_Trending/plugins125/plugins本文是 Cursor TypeScript SDKcursor/sdk错误处理主题的深度指南。围绕 error-handling.md 提出的核心模型展开集成中最常见的 bug 根源是把Agent 没能启动与Agent 执行了工作但工作失败当成同一种错误——它们根本不是一回事。读完本文你将掌握CursorAgentError的完整错误分类、RunResult.status的三种终态处理、可重试与不可重试错误的判别依据、生产环境日志与排障的最小方案以及一套可直接复制到 CI、定时任务和后台服务中的健壮错误处理代码。两大故障轴先把失败分成两类cursor/sdk的错误处理建立在一个非常朴素但极易被忽略的二维模型上。任何一次agent.send(prompt)的失败都落在下面两个轴之一didnt start started, didnt finish cleanly ──────────── ────────────────────────────── throws CursorAgentError returns RunResult { status: error | cancelled } .isRetryable .id (look it up in the dashboard) .code / .protoErrorCode .durationMs .git .result启动失败didnt startSDK 抛出CursorAgentError。原因通常是认证、配置或网络问题Agent 从未真正开始执行。运行失败started, didnt finish cleanlyrun.wait()正常返回一个RunResult但其中的status是error或cancelled。Agent 已经执行了至少部分工作只是没有干净地收尾。两种失败必须同时处理。只有try/catch拦不住运行失败——result.status error时不会抛异常只检查result.status又拦不住认证失败——AuthenticationError在send()阶段就抛出了。SKILL.md 中给出了一对一的对照示例CursorAgentError抛出 → 运行从未执行auth/config/network用退出码 1 表达result.status error→ Agent 做了工作但工作失败用退出码 2 表达只有finished才是退出码 0。这让 CI 失败变得可读运维一眼就能分辨认证失败和Agent 干活干坏了。完整的两路处理骨架下面这段代码把两轴都覆盖了并建议用await using确保Agent被正确释放SDK 持有本地执行器、持久化运行存储和云端 API 客户端的句柄不释放会泄漏子进程、数据库连接长期运行的服务会内存增长import { Agent, CursorAgentError } from cursor/sdk; async function runOnce(): Promisevoid { await using agent Agent.create({ /* ... */ }); try { const run await agent.send(prompt); const result await run.wait(); switch (result.status) { case finished: console.log(ok: ${result.id}); return; case cancelled: console.warn(cancelled: ${result.id}); return; case error: throw new Error(run ${result.id} failed after executing; inspect run state); default: { const _exhaustive: never result.status; throw new Error(unexpected status: ${_exhaustive}); } } } catch (err) { if (err instanceof CursorAgentError) { console.error(startup error (${err.constructor.name}): ${err.message}); if (err.isRetryable) { // Backoff-and-retry path } throw err; } throw err; } }switch中的default分支用const _exhaustive: never result.status做穷尽性检查将来若 SDK 新增状态而你的代码没处理TypeScript 编译期就会报错而不是运行时悄悄漏掉。CursorAgentError子类型全表SDK 抛出的所有错误都继承自CursorAgentError。具体子类就是决策信号——检查具体子类来决定怎么处理而不是笼统 catch 一个基类。类典型 HTTP 状态含义处理方式AuthenticationError401key 无效/过期/缺失或权限不足修复CURSOR_API_KEY参见 auth.mdRateLimitError429命中请求或用量上限Backoff 退避错误自带isRetryableConfigurationError400/404模型 id 错误、请求格式错误、资源不存在不要重试修复调用本身NetworkError503/504上游超时、瞬时基础设施故障若isRetryable则带抖动重试UnknownAgentError—无法按 proto code 或 HTTP code 归类记录并上报检查.cause中的原始ConnectError注UnknownAgentError的Typical HTTP列原文档标注为—因为它的分类既不依赖 proto code 也不依赖 HTTP code属于兜底类型。所有错误子类共有的字段无论哪个子类都携带以下五个字段它们是排查和决策的基础message—— 面向用户的描述已剥离 Connect 的[unknown]前缀isRetryable——来自后端的事实不是客户端启发式判断。后端告诉你这次失败是否安全重试code—— 底层 Connect/gRPC 的Code在相关时存在protoErrorCode—— 后端细粒度错误码稳定枚举值cause—— 原始ConnectError供深度调试不要泄露给最终用户。在 SKILL.md 的 Production Best Practices 中还强调尊重err.isRetryable是后端告诉你这次失败可以安全重试。盲目重试已失败的云运行会产生重复的云运行和重复的 PR尊重该标志则不会。一个特殊子类型UnsupportedRunOperationError它拥有独立基类——因为它描述的是SDK 本身的问题而非后端问题。当你在一个不支持该操作的Run上调用run.stream()、run.wait()、run.cancel()或run.conversation()时抛出。最常见的触发场景在 live 事件存储已关闭之后从Agent.getRun(...)拿到的 detached分离的句柄上调用cancel()/stream()。云端conversation()是支持的——它会从流中尽力累积对话。永远优先用run.supports(...)而非try/catch来判断能力if (run.supports(cancel)) { await run.cancel(); } else { console.warn(cancel not supported: ${run.unsupportedReason(cancel)}); }这一点与 streaming.md 的 Cancellation 一节相互印证run.cancel()在本地和云端运行都支持对云端它会 POST 到服务端的 cancel 端点并用服务端权威响应调和本地状态。但 detached/replayed 的 run 句柄来自Agent.getRun(...)可能没有 live 取消通道——run.supports(cancel)是防御性的正确姿势。重试模式该重试的与不该重试的判断要不要重试不是靠拍脑袋而是靠isRetryable标志加错误子类双重约束。应该重试带退避 抖动NetworkError且isRetryable trueRateLimitError且isRetryable true少见通常后端希望你等得比重试循环更久UnknownAgentError且isRetryable true—— 后端明确告诉你这是瞬时的。不要重试AuthenticationError—— key 不会自己变好ConfigurationError—— 坏输入不会自己变好任何isRetryable false的错误 —— 后端告诉你这是终态的。Agent 启动的重试次数要小≤3 次因为重新启动 Agent 代价很高。如果第一次尝试就遇到RateLimitError且isRetryable true至少退避 30 秒。带指数退避与抖动的Agent.create重试import { Agent, CursorAgentError, RateLimitError, NetworkError } from cursor/sdk; async function createWithRetry(options: Parameterstypeof Agent.create[0]) { const maxAttempts 3; for (let attempt 1; attempt maxAttempts; attempt) { try { return Agent.create(options); } catch (err) { const retryable err instanceof CursorAgentError err.isRetryable (err instanceof NetworkError || err instanceof RateLimitError); if (!retryable || attempt maxAttempts) throw err; const backoffMs 2 ** attempt * 1000 Math.random() * 500; await new Promise(r setTimeout(r, backoffMs)); } } throw new Error(unreachable); }这里退避公式2 ** attempt * 1000 Math.random() * 500是指数退避2s、4s、8s……叠加 0–500ms 随机抖动避免多个并发调用在同一时刻同时重试造成惊群。一个关键陷阱Agent.create是惰性的Agent.create是惰性的——在send()之前不会触达后端。因此绝大多数启动错误实际上是在send()时浮出来的。重试应该包住agent.send(...)而不只是Agent.create(...)。单独重试 create 只是创建了本地句柄并不会真正让失败的认证/网络请求重来一遍。RunResult.status error该怎么处理当运行至少部分执行、撞上 Agent 无法恢复的问题并上报错误时result里没有堆栈stack trace。可用的信号是result.id—— 运行 ID。用Agent.getRun(result.id, { runtime: cloud, agentId, apiKey })云端或Agent.getRun(result.id, { runtime: local, cwd })本地取回 Run然后读run.conversation()看 Agent 到底尝试了什么result.durationMs—— 如果是 0 或极小说明失败发生在非常早期不太可能是运行时问题result.git—— 云端运行时告诉你失败前是否已经创建了分支result.model—— 确认实际跑的是哪个模型在同时测试多个模型时很有用。通常不要对status: error自动重试。Agent 已经烧掉了 token 并朝着某个方向提交了决策盲目重试大概率会重复同样的事情。正确的设计是面向人工分诊human triage记录 ID、给出 dashboard 链接、升级给人类处理。重试在以下场景是站得住脚的你在做批量/扇出fan-out工作小比例的错误率可以接受prompt 是纯只读且幂等的你已经检查过对话记录确认失败是环境性的例如某个 flaky 的 MCP server。这与 patterns.md 中的 fan-out 模式一致那里用Promise.allSettled让单个仓库失败不拖垮其他任务并把失败记录为{ repoUrl, error: err.constructor.name, message: err.message }结构返回而不是直接 throw 中断整个批次。patterns 中还强调对已失败的云运行盲目重试会 spawn 重复的 PR——这是 fan-out 场景下最需要避免的。status: cancelled该怎么处理运行在成功执行run.cancel()本地或云端之后会报告这个状态。把取消视为非致命non-fatal记日志、做清理、继续往前走。对于在服务端被取消的云运行例如通过 dashboard 或另一个调用方取消你最终wait()时也会看到cancelled。此时注意取消之后要继续消费 stream 直到它结束——你会看到一个终态status事件而run.wait()会以status: cancelled解析。不要因为先看到cancelled状态事件就跳过wait()wait()返回的RunResult里才有 usage/duration/git 等流里拿不到的信息这一点在 streaming.md 中同样被强调。生产环境调试最少日志清单任何时候都要至少记录以下五项——它们足以把用户报告的任何问题与 Cursor dashboard 中的具体运行关联起来agent.agentId—— 在 create/resume 之后、任何send()之前记录run.id—— 在send()之后、流开始之前记录result.status、result.durationMs、result.git—— 在wait()之后记录出错时完整的err.message、err.constructor.name、err.isRetryable以及仅限内部日志err.protoErrorCode。SKILL.md 的生产最佳实践把这条逻辑补得更完整在send()之后、流开始之前立即记录run.id和agent.agentId—— 如果流挂起这两个 ID 是你通过 dashboard 或Agent.getRun(...)调查的唯一抓手。此外对于云端 agent IDbc-前缀要特别小心云 agent ID 不是 run ID。如果日志里只有 run ID来自日志或 webhook把它传给Agent.getRun时带上 runtime 提示即可不要把两者混淆。在 patterns.md 的 GitHub Action 代码审查模板中可以看到这套日志清单的落地形态console.log([review] agent${agent.agentId} run${run.id})紧跟send()随后循环 stream 打印 status 和 tool_call 事件最后run.wait()后检查result.status ! finished并以退出码 2 结束。它还给重试型启动失败分配了专用退出码 75EX_TEMPFAIL——比笼统的退出码 1 更能表达瞬时错误值得 CI 重跑。Dont四件绝对不要做的事原文档以一组反模式收尾每一条都对应一个真实事故不要对每个错误都process.exit(1)。RateLimitError且isRetryable: true时它要的是一个退避循环而不是进程退出。不要把err.cause记给最终用户。它是带内部字段的原始ConnectError。给人类看err.message把err.cause只留给内部可观测性系统。不要静默吞掉CursorAgentError把它降级成一个笼统的console.warn。子类本身就是信号——AuthenticationError、RateLimitError、ConfigurationError的处理方式完全不同笼统吞掉等于丢失全部信息。即使isRetryable: true也不要重试AuthenticationError超过一次——它几乎总是意味着 key 依然是坏的。重试只是在浪费时间和触发更多 401。在认证层面auth.md 补充了与AuthenticationError直接相关的排查表首次send()时报AuthenticationError通常是 key 缺失、过期或格式错误包括多余空白报ConfigurationError: BAD_USER_API_KEY则说明 key 语法无效只在云端报 401 而本地正常说明 key 无法触达 cloud agents surface可能是权限问题。这些症状与本文的CursorAgentError子类型表一一对应排查时建议对照阅读。何时重读配套参考文档本主题在整个 skill 中的定位是错误与重试决策。如果你的任务落在以下场景建议按需打开对应参考文件调试认证问题401、缺失CURSOR_API_KEY、team key vs user key、本地与生产环境差异→ auth.md选择本地/云端运行时判断错误发生的运行环境 → runtime-choice.md消费流、选择事件类型、取消、stream vs wait 的取舍 → streaming.md配置 MCP serverHTTP、stdio、云端 vs 本地传输、认证注入→ mcp.md子代理、resume、artifacts、列举/检查 agent → advanced.md组装具体集成CI 审查机器人、定时分诊、聊天、webhook→ patterns.md整套 skill 的入口与快速入门 → SKILL.md。总结cursor/sdk的错误处理只有一条主线启动失败抛CursorAgentError与运行失败RunResult.status是两种完全不同的故障必须分别建模、分别处理。实践层面归结为四个动作用switch穷举result.status三种终态用err instanceof 子类加err.isRetryable决定是否退避重试上限 3 次指数退避加抖动对status: error默认走人工分诊而非盲目重试无论成败都记录agent.agentId、run.id、result.status/durationMs/git五元组。把这些落到代码里你的集成就从碰运气变成了可排查、可恢复、可告警。【免费下载链接】pluginsCursor plugin specification and official plugins项目地址: https://gitcode.com/GitHub_Trending/plugins125/plugins创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。