• 简体中文
  • 错误处理

    错误可能出现在四层:HTTP 状态码、统一返回结构、JSON-RPC 的 error 对象,以及 MCP tools/call 结果中的 isError 标记。

    通常按以下顺序检查,再看具体 API 的字段:命令看 statusexit_code,文件看 data.error_type,代码单元看 outputs。流式接口例外:WebSocket 可能在握手成功后才报告错误。

    失败位置

    不同 plane 的失败表现并不相同,这是有意设计的。例如,退出码为 1 表示命令已经执行,只是命令本身失败;而不存在的文件则根本没有读成功。

    Plane操作失败时返回接着读
    文件,v2表达失败类别的状态码data.error_typedata.errno_name
    文件,v1200success: falsedata.error_typedata.errno_name
    Watch404400429503message
    命令、终端200success: truedata.status,然后 exit_code
    代码、Node.js、Jupyter200success: falsedata.statusoutputs[].ename
    浏览器没有 CDP 是 503,选择器匹配不到元素是 404message
    桌面 GUI没有 worker 是 503,操作系统拒绝输入是 403data.reason
    下载成功返回原始字节,失败返回状态码状态码
    MCP 工具200,结果上带 isError: truecontent 里的文本
    • 非零退出码不是传输层错误。例如执行 exit 3 时,POST /v2/commands 仍返回 200success: truestatus: "completed"exit_code: 3
    • 如果等待时间到了但命令仍在运行,返回 status: "running",而不是超时错误。
    • 代码单元失败时,响应返回 200success: falsedata.status"error",错误信息在 outputs 的 traceback 中。
    • 不存在的会话或命令 id,在各执行 API 上都返回 404。 WebSocket 会先以 101 完成握手,再发送 {"type": "error", "data": "Session not found"} 并关闭连接。监听器的 SSE 流会在建立连接前返回 404

    返回结构

    v1 和大多数 v2 接口共用同一个返回结构:

    {
      "success": true,
      "message": "Operation successful",
      "data": {},
      "hint": null
    }

    success: false 表示 aiod 已理解请求,但操作失败。此时读取 message;如果 hint 不为空,也一并读取。它不是传输层错误。

    校验错误

    请求未通过 schema 校验时返回 422,每个 plane 都一样:

    {
      "success": false,
      "message": "Request data validation failed",
      "data": null,
      "errors": [
        {
          "location": [
            "body",
            "command"
          ],
          "message": "missing field `command` at line 1 column 2",
          "type": "value_error.missing"
        }
      ]
    }

    query 字段使用同一套校验。字段缺失时,location["query", "path"];类型错误时,location 只有 ["query"]

    反序列化器会给出错误原因(例如 invalid digit found in string),但不会指出具体字段名。

    请求体缺失按 {} 处理:字段全是可选的路由可以不带 body 调用,带必填字段的路由则返回校验错误。

    文件错误

    文件接口的失败带一个结构化的 data 对象,而不是一段 message 文本:

    {
      "success": false,
      "message": "Failed to read file: No such file or directory (os error 2)",
      "data": {
        "errno": 2,
        "errno_name": "ENOENT",
        "error_type": "not_found",
        "exception_type": "FileNotFoundError",
        "message": "Failed to read file: No such file or directory (os error 2)",
        "operation": "read",
        "path": "/workspace/missing.txt",
        "retryable": false
      },
      "hint": null
    }

    /v2/fs/v1/file 返回相同的 data 对象,区别只在外层 HTTP 状态码。

    retryable 用来区分两类失败:锁被短暂占用等临时性失败,重试可能成功;路径写错等持续性失败,在修正原因前重试不会成功。

    error_type 与状态码的对应关系见 File(文件)

    HTTP 状态码约定

    状态码含义典型场景
    400参数或操作不合法lang 取值未知、锚点匹配到两处
    401未携带 key 或 key 无效设置了 AIO_API_KEY 时访问任何受保护路由
    403这次操作被拒绝daemon 账户读不了的文件
    404路由或对象不存在已下线的端点、未知的会话或命令 id
    409目标已存在复制或移动到已存在的路径
    413请求体过大超过 8 MiB 的 MCP 调用、超过 4 GiB 的 tar body
    422校验失败请求体或 query 字段缺失、类型不对
    429数量达到上限第 21 个原生代码会话
    501此 daemon 未实现在 Windows 上调用仅 Linux 的路由
    503对应能力现在不可用宿主机没有 bash

    这些状态码在不同路由上可能有多种触发原因:

    状态码常见原因
    400参数或操作不合法:lang 取值未知、锚点不唯一,或尝试修改已有会话的 user
    403权限不足:daemon 账户无法访问目标路径,或 Windows secure desktop 拒绝模拟输入;后者的响应中 data.statusdenied
    422请求参数或文本格式不正确:query 字段缺失或类型错误、不支持的 language,或读取非 UTF-8 文件
    429资源数量超限:第 6 个 kernel 会话、第 21 个原生代码会话,或第 129 个文件监听器
    501功能不可用:未安装 ipykernel 时调用 /v1/jupyter/*,或对内嵌 kernel 调用 interrupt/restart
    503依赖的能力暂不可用或等待超时:没有解释器、computer-use worker 已停止、没有 display,或 wait 超时

    MCP 工具错误

    tools/call 在工具内部失败时,仍返回正常的 JSON-RPC 结果,只是带有 isError 标记。这不是 HTTP 错误:传输成功了,但工具执行失败。

    isError 包括以下情况:

    • 参数缺失或非法,例如 sandbox_execute_bash 不带 cmd 时返回 cmd is required
    • 文件 API 的任何失败,此时结构化错误对象会作为结果文本返回。
    • CDP 端口无响应时调用 browser_get_info,或没有 computer-use worker 时调用 browser_gui_screenshotbrowser_gui_execute_action
    • 调用 EXTRA_MCP_SERVERS 中已停止的上游。此类上游也不会出现在 tools/list 中。

    名字不存在则属于协议错误:/mcp 返回 200,响应体是 JSON-RPC 的 error 对象,方法名不存在是 -32601,工具名不存在是 -32602。请求体不是 JSON 则是 -32700

    客户端处理

    返回结构和 plane 自己的执行结果是两层信息,都需要检查。客户端通常只负责处理 success: false;退出码和代码单元状态仍需由调用方读取:

    from agent_sandbox import Sandbox
    
    client = Sandbox(base_url="http://127.0.0.1:18091")
    
    missing = client.file.read_file(file="/tmp/demo/missing.txt")
    print(missing.success, missing.data.error_type, missing.data.retryable)
    # False not_found False
    
    command = client.bash.exec(command="exit 3").data
    print(command.status, command.exit_code)
    # completed 3
    
    cell = client.code.execute_code(language="python", code="1/0")
    print(cell.success, cell.data.status, cell.data.outputs[0]["ename"])
    # False error ZeroDivisionError

    同样三次读取的 TypeScript 版本:

    import { SandboxClient } from "@agent-infra/sandbox";
    
    const client = new SandboxClient({ environment: "http://127.0.0.1:18091" });
    
    const missing = await client.file.readFile({ file: "/tmp/demo/missing.txt" });
    const failure = missing.body?.data as any;
    console.log(missing.body?.success, failure.error_type, failure.retryable);
    // false not_found false
    
    const run = await client.bash.exec({ command: "exit 3" });
    const command = run.body?.data as any;
    console.log(command.status, command.exit_code);
    // completed 3
    
    const cell = await client.code.executeCode({ language: "python", code: "1/0" });
    const result = cell.body?.data as any;
    console.log(cell.body?.success, result.status, result.outputs[0].ename);
    // false error ZeroDivisionError

    遇到 retryable: true503 可以重试;429 则应退避后再试。400404409422 通常不会自行恢复,直接重试没有意义。

    相关页面