Show HN:Corral —— 杀掉你的 AI agent 启动的所有命令

Hacker News Top 工具

摘要

Corral 是一个 Linux 命令行工具,给命令设定时间限制,并保证其衍生的任何进程都不会存活下来。它使用 cgroup v2(`cgroup.kill`),或退而求其次采用 subreaper + 扫描 `/proc` + pidfds 的方案。它面向的是那些会泄漏守护进程、僵死管道以及无视 SIGTERM 的 AI 编码 agent 和 CI 任务。

今年夏天我在一个 AI agent 的后端实习,注意到它会执行 tail -f 之类的命令或是一些随机的后台任务,然后直接退出,完全不终止这些进程。我有点好奇,就去查了下 Claude Code 是怎么处理这类问题的,结果发现他们也有类似的问题。<p>有一些 issue 提到 Bash 工具启动的后台进程在会话结束时没有被清理掉,还有一个 issue 是说超时后向整个进程组发送 SIGTERM,结果把 Claude Code 自己也杀了。大多数运行器只会杀掉它自己启动的那个进程,所以任何二次 fork 的进程、转到后台运行的进程,或者一直保持 stdout 打开的进程,要么存活下来,要么让运行器挂住。<p>Corral 就是我尝试修复这个问题时写的工具,同时也是为了学习 Linux 进程和信号相关的知识。你运行 corral --wall 30s -- yourcommand,等它结束时,它启动的所有东西都应该已经不在运行了。<p>命令会运行在自己的会话里,所以杀掉它不会误伤到你;另外还有一个模式,让命令运行在自己的 cgroup 中,这样整棵进程树会被一次性杀掉,包括那些可能已经换了会话或进程组的进程。<p>如果没有可用的 cgroup,它会改用 &#x2F;proc 来跟踪进程树(这个方案弱一些:父进程死后,它仍然能捕获那些只改过一次会话的进程,但无法杀死以其他用户身份运行的进程,也无法杀死已移交给 systemd 之类的其他服务的进程),如果无法确认所有进程都已退出,它会以退出码 120 结束。<p>非常欢迎任何反馈,也希望能了解到更多这类我可以用来继续学习的东西
查看原文
查看缓存全文

缓存时间: 2026/09/30 20:04

Cardinal44/corral

来源:https://github.com/Cardinal44/corral

corral

corral 用于在时间限制下运行命令。当 corral 返回时,该命令启动的所有进程都已不复存在。corral 会在返回前验证这一点。如果无法证明这一点,corral 将以退出码 120 退出。

AI 编码代理和 CI 任务运行的往往是它们并未编写的 shell 命令。典型的运行器会向其子进程或子进程组发送信号,然后等待输出管道关闭。这种模型在以下三种常见场景中会失效:

  • 守护进程 fork 两次并调用 setsid()。此时它处于不同的进程组和会话中,其父进程是 init,因此信号无法送达。
  • 后台进程持续持有 stdout 或 stderr。运行器永远不会读到文件结束符,因此在命令退出后它会挂起。
  • 某进程忽略 SIGTERM 并继续运行。

残留进程会占用端口、文件锁和 CPU,可能导致下一次运行失败。

corral 控制的是完整的进程树,而不仅仅是其直接子进程。在强制模式下,命令运行在自己的 cgroup v2 组中,向 cgroup.kill 写入一次即可停止该组中的所有进程。在没有 cgroup 的情况下,corral 作为子进程的 subreaper 运行,孤立进程会成为它的子进程。corral 通过 /proc 找到剩余的进程,并通过 pidfd 向它们发送信号。在这两种模式下,运行都在命令退出时结束,而不是在管道关闭时结束。

corral 不是安全沙箱。它不限制文件访问、网络访问或权限。

安装

corral 需要 Linux 5.11 或更高版本,强制模式需要 5.14 或更高版本。

预编译二进制文件适用于带 glibc 2.36 或更高版本的 x86-64 系统(例如 Ubuntu 22.10、Debian 12、Fedora 37 或更新的发行版):

curl -LO https://github.com/Cardinal44/corral/releases/latest/download/corral-linux-x86_64.tar.gz
tar -xzf corral-linux-x86_64.tar.gz
sudo cp corral-linux-x86_64/corral corral-linux-x86_64/corral-enforced /usr/local/bin/

从源码构建需要 glibc 2.36、CMake 3.22、Ninja 以及 GCC 11 或 Clang 14。测试需要 Python 3.10。

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure

使用

corral --wall 30s -- ./build.sh
corral --wall 5m --max-output 1M --json run.json -- make test
corral-enforced --wall 30s --mem 512M --pids 64 -- ./build.sh

corral 有两种模式:

  • 强制模式将命令放入自己的 cgroup v2 组中。内核会将所有后代进程保留在该组内,向 cgroup.kill 写入一次即可停止所有进程。此模式还会应用 --mem 和 --pids。它需要一个已委托的 cgroup。scripts/corral-enforced 使用 systemd-run 启动一个。
  • 回退模式不需要 cgroup。corral 通过父进程、进程组和会话找到进程,并扫描 /proc 直到没有残留进程。

默认的 --cgroup-mode=auto 在条件允许时使用强制模式。

选项默认值含义
--wall DURATION无运行的时间限制。
--grace DURATION2sSIGTERM 与 SIGKILL 之间的间隔时间。
--verify-timeout DURATION2s最终检查的时间限制。
--mem SIZE无内存限制(强制模式)。
--pids COUNT无进程数量限制(强制模式)。
--max-output SIZE无stdout 和 stderr 合计的输出限制。
--cgroup-mode MODEautoauto、enforced 或 fallback。
--cgroup-parent PATH无用于 corral 创建的组的已委托 cgroup。
--stdin POLICYnullnull 或 inherit。
--json PATH无将审计记录写入 PATH。
--quiet关闭不打印摘要行。

DURATION 是带有 ms、s、m 或 h 后缀的数字。SIZE 是以字节为单位的数字,可带有可选的 K、M 或 G 后缀。

使用 --json 时,corral 为每次运行写入一条 JSON 记录。该记录包含模式、限制条件以及运行结束的原因,还包含每个步骤的时间以及任何未停止进程的 pid。

退出码

退出码含义
命令的退出码命令正常退出。
128 + N信号 N 终止了命令,或 corral 收到了信号 N(Ctrl-C 为 130)。
124时间限制到期。
121达到内存限制。
122达到输出限制。
126、127corral 无法启动命令(127:未找到)。
125设置失败或某个选项不正确。命令未启动。
120corral 无法证明所有进程都已停止。此退出码优先于其他所有退出码。

工作原理

  • 命令在新会话中启动,因此发送给其进程组的信号永远不会到达 corral。
  • corral 是子进程的 subreaper。孤立进程会成为 corral 的子进程,因此 corral 可以停止它们。
  • corral 只有在再次确认进程后,才通过 pidfd 向不是其子进程的进程发送信号。被复用的 pid 永远不会收到信号。
  • 运行在命令退出时结束,而不是在其输出管道关闭时结束。
  • 每次运行结束后,corral 会持续停止并回收进程,直到没有残留为止。在强制模式下,内核还必须报告该组为空,且对该组的 rmdir 操作必须成功。

结果

bench/compare.py 使用四种运行器运行了 faults/ 中的十个测试程序:

  • CE:强制模式下的 corral
  • CF:回退模式下的 corral
  • TO:timeout -k 1s 2s
  • NV:一个只终止直接子进程的 Python 运行器

每次运行的时间限制为 2 秒。每个单元格显示 5 次运行中的退出状态和仍然存活的最大进程数。

测试程序CECFTONV
F1 每 100 毫秒打印一行124 / 0124 / 0124 / 0137 / 0
F2 等待 stdin0 / 00 / 0124 / 0137 / 0
F3 启动后台作业0 / 00 / 00 / 1挂起 / 1
F4 启动守护进程并退出0 / 00 / 00 / 1挂起 / 1
F5 启动守护进程并保持运行124 / 0124 / 0124 / 1挂起 / 1
F6 忽略 SIGTERM124 / 0124 / 0137 / 0137 / 0
F7 fork 200 个子进程124 / 0124 / 0124 / 1挂起 / 192
F8 子进程持有 stdout0 / 00 / 00 / 1挂起 / 1
F9 使用 256 MiB121 / 0124 / 0124 / 0137 / 0
F10 在收到 SIGTERM 时清理124 / 0124 / 0124 / 0137 / 0

corral 在所有测试中都没有留下任何存活的进程。CE 还在 0.12 秒内按 64 MiB 限制终止了 F9。TO 在 5 次运行中有 1 次在 F7 中留下了存活进程。在这台机器上,timeout 来自 uutils coreutils 0.8.0,而非 GNU coreutils。

bench/bench.py 测量了以下耗时(P50 / P95):

测量项强制模式回退模式
停止 F5:从时间限制到验证完成9 / 12 ms10 / 12 ms
运行 /bin/true(直接调用:0.83 / 1.88 ms)11.33 / 19.47 ms6.64 / 10.01 ms

测试机器:Hetzner 云服务器,2 个共享 vCPU(Intel Xeon,Skylake),Ubuntu 26.04,内核 7.0。完整数据和 scripts/demo.sh 的终端录制位于 bench/results/ 中。

局限

  • corral 无法看到命令在其进程树之外启动的工作,例如通过 systemd-run、D-Bus 或 at 启动的进程。
  • 在强制模式下,如果进程自己向 cgroupfs 写入,它就可以脱离该组。
  • 在回退模式下,corral 无法向 setuid 子进程发送信号。此时它会以 120 退出。
  • 如果使用 SIGKILL 停止 corral,只有直接子进程能确定被终止。
  • corral 不支持 PTY,也没有 CPU 时间限制,且仅能在 Linux 上运行。

许可证

MIT。参见 LICENSE。

相似文章

Show HN: Claw Patrol,面向AI Agent的安全防火墙

Hacker News Top

Claw Patrol 是一款面向AI Agent的安全防火墙,位于Agent与生产环境之间,通过HCL规则解析流量并管控行为,可拦截破坏性SQL操作,或对kubectl delete pod等风险命令要求人工审批。

Show HN: Offrun – 从一个工作区管理所有编程智能体

Hacker News Top

Offrun 是一款免费的 macOS 应用,可从单一工作区管理多个编程智能体 CLI,为每个智能体分配独立的 git worktree,支持基于智能体的 diff 同行评审、账号负载均衡、项目记忆文件以及本地语音听写——全部在本地运行,使用用户自己的智能体账号。

Show HN: 为你的Claude Code代理打造的警察部门

Hacker News Top

agent-pd 是一个开源的日志和监控工具,用于 Claude Code 代理,它记录所有工具和权限事件,并通过确定性检测器重放这些事件以捕获违规行为,而不会阻止任何操作。