2026年8月20日 1 分钟阅读

给 AI Agent 的 Bash 操作加一层可撤销记录:doover 的快照、日志与恢复边界

tinyash 0 条评论

AI 编码 Agent 进入日常开发后,真正让人紧张的往往不是它写错一段代码,而是它在 Bash 里执行了一条“看起来合理”的清理命令:删掉未跟踪的测试数据、用 rsync --delete 同步错方向,或在错误目录运行 git reset --hard。这些动作通常不经过编辑器的撤销栈;Git 也只能保护已纳入版本控制的内容。

doover 是一个面向这类风险的 Rust 工具。它目前通过 Claude Code 的 PreToolUsePostToolUse hooks 介入:命令真正执行前,先判断会影响哪些本地路径并创建快照;命令结束后,再把动作写入 SQLite 日志。它不替你批准或阻断命令,而是试图把可逆的文件系统操作变成可查询、可恢复的记录。

这与“给 Agent 加权限确认”是两条不同的安全路线。审批适合高风险操作前的人为决策;doover 更像安全带,适合你已经允许 Agent 高频运行命令、但希望把误操作的恢复成本降下来。它的代码以 Apache-2.0 发布,规则数据以 CC0 发布;当前 hook 接线目标是 Claude Code,核心与日志存储本身则不依赖某个模型。

Git、沙箱和 checkpoint 各自留下了什么空档

把 doover 当成 Git 的替代品会误判它的定位。三者处理的问题不同:

  • Git 擅长恢复已提交或已纳入工作区管理的源码;忽略文件、未跟踪目录、本地数据库和仓库外路径不在其保护范围内。
  • 沙箱 把影响面限制在工作区或容器中,但工作区内部被删除的数据仍可能无法找回。
  • Claude Code checkpoint 覆盖其文件编辑工具产生的变更;README 明确指出,Bash 工具运行的 rm -rf 不在该 checkpoint 范围内。
  • doover 在命令执行前保存文件系统状态,并为动作建立日志;它试图补的是“已获准的 shell 操作仍会误伤本地文件”的缺口。

因此,一个较稳妥的组合是:Git 用来保存项目历史,备份系统应对机器、磁盘与长期留存,沙箱约束 Agent 可接触的范围,doover 处理本地 shell 误操作的短期可逆性。每层都有边界,不能把其中任何一层当成全部防线。

最小安装与接入:先让 hook 自检

doover 支持 macOS、Linux 与 WSL;原生 Windows 不在 README 的支持范围内。若本机已经有 Rust 1.85 或更高版本,可以用 Cargo 安装:

cargo install doover --locked
doover init
doover doctor

doover init 会把 hook 配置写入 ~/.claude/settings.json,并且会和已有设置合并而非重复添加。若只希望在一个仓库内启用,使用项目级配置:

doover init --project
doover doctor

doctor 不只是形式检查:它用于确认 hook 端到端可工作。安装后不要立刻让 Agent 执行大范围清理;先在临时目录里让它创建一个普通文件、删除该文件,再检查日志和恢复流程。这样能同时验证 Claude Code 的 hook 配置、快照目录权限与磁盘空间是否符合预期。

Homebrew 用户也可以按官方 README 的 tap 与安装步骤部署;发布页提供预编译二进制及 SHA256SUMS。在团队环境中,更建议把安装方式、版本与 doover doctor 的检查步骤写进开发环境文档,而不是只依赖个人机器上的一次性配置。

一次删除如何变成可恢复的动作

典型场景是 Agent 收到“清理构建产物”的自然语言任务,却把范围扩大到了包含本地照片或测试夹具的目录。接入后,执行链路大致如下:

  1. PreToolUse hook 接收 Bash 命令;
  2. doover 用 Bash 解析器分析命令、管道、重定向、glob 与引号,判断路径和风险;
  3. 对已识别的破坏性操作,先将受影响路径快照到 ~/.doover/store
  4. 记录动作后才让原命令继续执行;
  5. PostToolUse 写入执行后的状态,之后可从日志选择恢复点。

例如,先查看近期动作,再撤销最近一次真正改动了文件的破坏性操作:

doover log
doover undo
doover status

需要定位某个历史动作时,使用 log 中的编号:

doover show 42
doover diff 42
doover undo 42 --dry-run
doover undo 42

先执行 --dry-run 很重要。doover 会检测冲突:如果目标文件在该动作之后又被其他命令或人工修改,默认拒绝覆盖,并以退出码 3 提示冲突。--force 可以绕过这一保护,但这意味着你主动选择用旧快照覆盖后续内容,应该只在确认影响范围后使用。恢复动作本身也会写入日志,因此误撤销后可用 doover redo 重新应用。

它如何判断“该保护什么”

doover 内置了 152 条 CC0 规则,用来区分安全、破坏性和不可逆的常见命令。例如 rmgit reset --hardmvteersync --delete 会按已分析到的路径创建快照;已证实只读的命令则不应产生快照开销。

./deploy.sheval "$X"python cleanup.py 这类无法充分静态解析的命令,默认策略是把当前工作目录作为预防性覆盖范围,并在日志中标记为尽力而为。这一点需要在团队里说清楚:脚本若删除了工作目录之外的路径,doover 的未知命令兜底并不能证明那些路径已被保护。

对于数据库删除、kubectl deletegit push --force 等远端或非文件系统效果,工具会在日志里标记不可恢复,但本地快照无法使它们“撤销”。同样,doover 不是恶意 Agent 防御机制;它基于静态分析,目标是降低失误伤害,而非对抗刻意规避规则的攻击者。

快照成本、容量和敏感数据的取舍

快照并不等于无成本。README 给出的默认值包括:单次快照最多 5 秒、最多 100,000 个文件、最多 5 GiB;整个存储区(快照加日志)默认上限也是 5 GiB,历史默认保留 7 天。空间超出上限后旧记录会被清理,最近一小时的记录不会因空间清理而被逐出;需要保留的动作可显式 pin。

doover pin 42
doover status
doover gc

在 APFS、Btrfs、XFS 上,项目会尝试使用 copy-on-write;相同内容则可按 BLAKE3 哈希去重。不过不能因此假设所有文件都便宜。大量小文件、非支持文件系统、或一次包含大型目录树的操作,仍可能让快照耗时或触及限制。README 还说明:整树快照会跳过 target/node_modules/.venv/dist/ 等目录,但只有目录名匹配且 Git 已忽略时才跳过;直接把命令指向这些目录时,仍会按目标捕获。

更容易被忽略的是数据安全:快照默认存放在 ~/.doover,目录与文件权限面向当前用户,但并非静态加密。命令日志会在写盘前做模式化的凭据脱敏,仍不应把它当作完整的 DLP。若项目含生产导出、个人资料或密钥文件,应评估快照目录所在磁盘、账号访问控制、保留期和备份策略;必要时调整 DOOVER_HOME 与容量、保留期等环境变量。

还应把“快照成功”与“恢复适合立即执行”分开看。doover 会在恢复时构建临时树再整体替换,以避免恢复中途崩溃留下半成品;但目录级恢复会替换目标目录,开发者若在该目录中有之后产生的手工修改,仍应先通过 doover diff doover undo --dry-run 确认差异。对多人共用工作目录、持续运行的本地服务或生成文件夹,这个确认步骤比直接追求“一键回滚”更可靠。

适合什么团队,何时不该依赖它

doover 的最佳位置是“开发者已经让 Claude Code 执行 Bash,但希望事故可回放”的本地工作流:临时数据较多、测试脚本会清理目录、或 Agent 会跨越多个未提交修改时尤其有价值。它也适合在团队共享的开发规范中,要求高破坏性任务先运行 doover status,恢复前先跑 --dry-run,并把 doctor 作为环境验收的一部分。

但它不适合作为数据库、云资源或 Git 远端的回滚承诺;也不能替代异地备份,历史受时间和容量上限约束,并且与原始文件在同一台机器上。更合理的结论是:当 Agent 的“自主执行”已经不可避免时,把本地 shell 操作补上一层可验证的恢复记录,能让很多本来只能靠祈祷和手工排查解决的事故,变成一条日志、一份快照和一次经过冲突检查的恢复。

相关链接

发表评论

你的邮箱地址不会被公开,带 * 的为必填项。