雨天小六

读懂 Codex(9.9):Shell Tool 的进程、超时、取消和输出截断

· 更新于 2026-08-03 · 专栏:读懂 Codex

#Codex#Agent Runtime#Python#软件架构

具体问题与边界

从 argv 参数到子进程结果,校验、Policy、Approval、执行、超时、取消和截断的真实顺序是什么?

ShellTool 负责输入与策略;ExecutionEnvironment 负责进程;CancellationToken 决定中断;ToolRouter 负责结果配对。 本节以官方 Codex 锁定提交为事实基线,以 Mini Codex 的可执行断言检验 Python 设计;二者不是逐行翻译关系。

协议、类型与状态所有权

对象创建/所有者生命周期与作用域是否持久化
argv/cwd/timeoutToolCall arguments单次调用随 ToolCall 持久化
ExecDecisionExecPolicy单次命令
Approval decisionApprovalPolicy/Host单次动作Mini 不单独持久化
subprocessExecutionEnvironment直到 exit/kill/reap
stdout/stderrExecutionEnvironment调用结果编码进 ToolResult

正常路径

Shell Tool 的进程、超时、取消和输出截断正常路径图
图 9.9-1:从 argv 参数到子进程结果,校验、Policy、Approval、执行、超时、取消和截断的真实顺序是什么?
  1. 参数先验证为非空字符串数组;Mini 不先拼 Shell 字符串,从而避免第二次 shell parse 改写 argv 边界。
  2. cwd 通过 WorkspacePolicy.resolve;这只限制启动目录,不能限制命令自己访问哪里。
  3. ExecPolicy 对 argv prefix 产生 allow/prompt/forbid。forbid 直接形成错误结果;prompt 才询问 Approval;allow 跳过人工询问。
  4. ExecutionEnvironment 用 create_subprocess_exec 创建进程,并并发等待 communicate、取消事件与 timeout。
  5. 正常退出收集 returncode;timeout kill+wait 并返回 124;Interrupt kill+wait 后抛 TurnCancelled。输出在环境层统一截断。

调用链与等待点

1. 参数先验证为非空字符串数组;Mini 不先拼 Shell 字符串,从而避免第二次 shell parse 改写 argv 边界
→ 2. cwd 通过 WorkspacePolicy.resolve;这只限制启动目录,不能限制命令自己访问哪里
→ 3. ExecPolicy 对 argv prefix 产生 allow/prompt/forbid
→ 4. ExecutionEnvironment 用 `create_subprocess_exec` 创建进程,并并发等待 communicate、取消事件与 timeout
→ 5. 正常退出收集 returncode;timeout kill+wait 并返回 124;Interrupt kill+wait 后抛 TurnCancelled

每个 await 都是状态可被取消、外部副作用可能已经发生或其他任务能够推进的边界。正文因此同时写清输入形状、所有者、成功产物和失败残留,而不是只列方法名。

Python 风格伪代码

async def shell(arguments, ctx):
    argv = require_string_array(arguments["command"])
    cwd = ctx.workspace_policy.resolve(arguments.get("cwd", "."))
    timeout = require_positive_number(arguments.get("timeout_seconds", 30))
    decision = exec_policy.decide(argv)

    if decision == FORBID:
        return ToolExecution("forbidden", is_error=True)
    if decision == PROMPT and not await ctx.approval.approve(argv, cwd):
        return ToolExecution("rejected", is_error=True)

    process = await create_subprocess_exec(*argv, cwd=cwd, pipes=True)
    io_task = create_task(process.communicate())
    cancel_task = create_task(ctx.cancellation.wait())
    done = await wait_first(io_task, cancel_task, timeout)
    if cancel_task in done:
        kill_and_reap(process); raise TurnCancelled()
    if io_task not in done:
        kill_and_reap(process); return timeout_result(124)
    return encode_result(process.returncode, truncate(stdout), truncate(stderr))

伪代码表达顺序和责任。真实可运行实现没有把万能调用当作未解释黑箱;对应模块见 Mini 导航。

失败、取消与恢复

Shell Tool 的进程、超时、取消和输出截断失败路径图
图 9.9-2:失败分支按实际状态所有者收口,模型可见失败与 Runtime fatal 分开。
故障点已发生/残留状态处理与模型可见结果
argv 类型错误不创建进程错误 ToolResult,模型可修参数
cwd 越界WorkspaceViolationRouter 编成 tool failed Result
Policy forbid/Approval deny没有副作用模型看到拒绝原因
timeout进程被 kill 并 waitexit_code 124 与错误文本
Interrupt进程被 kill/reapRouter 补 aborted,Turn owner Aborted
输出过大内存/Prompt 膨胀在执行边界截断并标明移除量

必须保持的不变量

任何进程终点都必须明确收集或 kill+reap;拒绝、超时与普通非零退出不能消失成无 Result 的 ToolCall。

设计取舍

argv 模式舍弃管道、重定向等 Shell 语法,换取清晰参数边界。官方支持更多 shell backend、sandbox escalation 与平台差异;Mini 的 Local adapter 不隔离恶意命令。

“为什么”的表述是从源码状态机、调用顺序和测试反推的工程解释;源码未声明的动机不写成官方承诺。

测试与复现实验

cd examples/mini-codex
uv run pytest -q -k 'test_approval_rejection_becomes_model_visible_result or test_exec_policy_uses_longest_argv_prefix or test_interrupt_produces_one_aborted_terminal_event'
uv run mypy src

关键断言:

  • test_approval_rejection_becomes_model_visible_result
  • test_exec_policy_uses_longest_argv_prefix
  • test_interrupt_produces_one_aborted_terminal_event

官方源码导航

Mini Codex 对照

  • src/mini_codex/tools/shell.py:输入/Policy/Approval/结果编码
  • src/mini_codex/execution/environment.py:timeout/cancel/kill/reap/truncate
  • src/mini_codex/policy/exec_policy.py:argv prefix 决策

本节边界

已经证明:任何进程终点都必须明确收集或 kill+reap;拒绝、超时与普通非零退出不能消失成无 Result 的 ToolCall。

阅读导航

上一节:9.8 · 下一节:9.10

评论


← 返回文章列表