Appearance
代码修改如何成为可验证动作:Shell、Patch 与 Command Lifecycle
前置:Coding Agent 的工作环境是什么:Workspace 与 Filesystem State
核心问题:一次 shell/patch 执行怎样从“模型意图”变成有 identity、timeout、输出和退出状态的可验证动作?
pytest 可在 workspace 内执行,但 curl 外网或删除父目录可被 sandbox/policy 阻断。Agent 对函数附近生成 unified diff;若原文件已变化导致 context mismatch,返回 conflict 让其重新读取。
Shell Tool
Shell 是 Coding Agent 最强也最危险的 Tool 之一,因为一条命令同时拥有文件、进程、网络和环境变量能力。
一个可控的 Shell Tool 至少要明确:
text
cwd / user / env
allowed network/filesystem scope
timeout + cancellation
stdout/stderr size limit
exit code / signal
approval requirement例如 pytest 超时后,不能只让上层停止等待而留下测试进程继续运行;Tool 必须能把 cancellation 传播到 child process,并记录最终 exit/signal。PTY 只有交互程序真正需要时再引入,否则会额外增加 session state。
Apply Patch / File Editing
编辑最好生成可审查 patch,apply 后读取 diff 并运行 targeted test;“工具返回成功”不是修改正确的证据。Patch 编辑应以明确 base state 为前提,最好保留 old/new diff、目标路径和 apply result。修改后必须重新读取关键片段并运行 formatter/test;并发修改时要检测 base hash/mtime 冲突,避免悄悄覆盖用户或其他 Agent 的变化。
Patch 接口应基于明确文件版本/上下文应用最小变更,并返回 diff/冲突。比“重写整个文件”更易审计和避免覆盖并发修改。
Command Lifecycle
命令从 created→running→exited/cancelled/timeout,有 stdout/stderr stream 和 process id。长命令需要异步观察、终止子进程树,并在 Agent disconnect 时定义是否继续。
🔍 源码对照:长命令如何在重连后继续被观察
精确定位
- 文件:
source/host/runner/shell-terminal-watch.ts - Symbol:
pollShellTerminalFile()→readShellTerminalSnapshot()→settle() - 深入索引:Code Atlas:13-shell-lifecycle
ts
async function pollCommand(id, isCancelled, settle) {
while (!isCancelled()) {
const box = await ensureBoxReady()
const snapshot = await readTerminalSnapshot(box, id)
const footer = parseTerminalFooter(snapshot.content)
if (footer.isComplete) {
settle(footer.exitCode === 0 ? "success" : "error")
return
}
await delay(pollInterval)
}
}对照理解
- Command completion 不是“调用 spawn 返回了”或“上层不再等待”,而是 Runtime 持续读取外部 execution artifact,直到看到可验证的 terminal footer。
pollShellTerminalFile()还处理 output file 丢失、读取权限被撤销、超长等待和 cancellation;这些都必须收敛成明确 outcome。- 这说明长命令需要稳定 execution id + 可重新定位的 output artifact。Host 重启后可以重新 watch,而不是把“进程仍在跑”误判成任务丢失。
Shadow Harness 对照
CommandExecution 至少保存 execution_id / status / output_ref / exit_code / timeout_or_cancel_reason;Resume 时先 reconciliation 外部 process/artifact,再决定状态。
Failure Modes
Shell 输出和进程生命周期无上限
症状: command 没 timeout/output cap,stdout/stderr 无限进入 Context。
为什么危险: 内存/context 被拖垮,cancel 后进程仍可能运行。
处理: 记录 PID/exit/timeout,限制 buffer,完整日志写 artifact,取消时终止 process tree。
Patch 成功就认为代码任务完成
症状: apply patch 成功后不跑 formatter/static/tests。
为什么危险: 文件写入成功不代表行为正确。
处理: patch 后执行 diff review + targeted/static/full tests,并保存结果。
如何验证
| Case | 断言 |
|---|---|
| Shell 输出和进程生命周期无上限 | 持续输出且不退出命令在 timeout 后 process tree 被清理。 |
| Patch 成功就认为代码任务完成 | 可应用但破坏测试的 patch 不能让 Run 进入 SUCCEEDED。 |
关联源码
自测
- 模型生成了一条 shell command 后,从“准备执行”到“结果可作为 Observation”中间要记录哪些 lifecycle 状态?
- Patch 命令返回 0,但目标文件内容与预期不符。为什么 exit code 成功不能替代 change verification?