它验证过才删的:agent 的 dry-run 是干净的,线上文件还是没了

作者:simon · 2026-10-15

我让 agent 帮我改一个页面,顺手部署。部署日志里蹦出两行:

perl 复制代码
delete: s3://<bucket>/news/daily-brief.json
delete: s3://<bucket>/news/manifest.json

这两个文件不在我的部署目录里,是线上另一个定时程序写的。我只想改一个页面。

我当时就懵了。

后来更让我没想到的是:它不是莽撞删的,它是"验证过"才删的。

先说结论

  • agent 部署前其实做过 dry-run(彩排),输出很干净,只有一行 upload,它据此判断"安全"。
  • 问题不在彩排这个动作,而在于:它彩排的,不是脚本真正会执行的那条命令。
  • 差在一层引号上:写进变量的引号,只会被 eval 解析一次;直接写在命令行上的,要被解析两次。层数对不上,--exclude 就失效了。

背景

  • 一个静态站,部署脚本用 aws s3 sync --delete 把本地目录同步到对象存储,再让 CDN 缓存失效。
  • 同一个桶里还有别的程序写的内容,比如 news/ 下面的文章和状态文件。它们在本地目录里永远不存在,所以 --delete 一旦碰到,就会当成"源里没有,该删"。
  • 脚本设计上,用 --exclude 把这些前缀排除在删除之外。
  • 工具是 Claude Code,模型 claude-sonnet-5-5。

过程

第一步:彩排,干净。 agent 先读了部署脚本,看到里面排除了 news/,认为不会误删。然后它手敲了一条 dry-run:

arduino 复制代码
(dryrun) upload: public/en/delete-account/index.html to s3://<bucket>/en/delete-account/index.html

只有一行上传,没有删除。AWS 文档对 --dryrun 的说明是:

"Displays the operations that would be performed using the specified command without actually running them."

第二步:真跑,删了。 紧接着它直接跑脚本本身,输出里就出现了开头那两行 delete。

第三步:评估。 先查桶有没有开版本控制:没有。也就是说删掉的东西没法一键还原。

这两个文件里,manifest.json 是文章清单,也是"每天最多发几篇"的计数来源。agent 读了定时程序的代码后推断:清单丢了,下一轮任务会以为"今天一篇都没发",再拿一个空清单去重建首页。

注意,这是它读代码得出的推断,我们没有真让任务跑一遍来验证。离整点任务触发,只剩十来分钟。

第四步:补救。 那十来分钟里,我在想怎么补救。

  • 先把定时任务暂停。agent 自己想暂停,被权限拦住了,我就在终端里自己敲了那条命令。
  • 公网上这两个文件都是 404,CDN 边缘也没有缓存副本,只能重建。
  • 办法是:从线上已经发布的 957 篇文章页里,读出标题、描述、发布日期,反推出 manifest.json,字段和程序原本写入的格式对齐,再传回去。957 条里没有缺标题或缺日期的。
  • 有一个字段只能近似:生成日期,我用页面的最后修改时间代替。后果是"今天已发篇数"会偏高,今天不再补稿,偏安全的一侧。
  • 重新启用定时任务,它立刻跑了一轮:新增 0 篇,清单还是 957 条,首页正常重建。

为什么会这样

eval 为什么要把引号解析两次

出错的那一行(脱敏后)长这样:

bash 复制代码
COMMON_EXCLUDES="--exclude '\"news/*\"' --exclude '\"_pv/*\"'"
...
eval aws s3 sync "$SRC" "s3://$BUCKET" $DEL $COMMON_EXCLUDES --exclude '"*.html"' ...

注意同一行里有两种写法:变量里的 '\"news/*\"',和直接写在 eval 行上的 '"*.html"'。后者是对的,前者是错的。

我用 printf 当替身(它会把"收到的每个参数"打印出来),在 bash 5.3 下实测:

bash 复制代码
== 变量里写 '"news/*"'(旧写法),经 eval
  argv: [--exclude]
  argv: ["news/*"]          <- 带着双引号的字面量
== 变量里写 'news/*'(新写法),经 eval
  argv: [--exclude]
  argv: [news/*]
== 直接写在 eval 那一行:--exclude '"*.html"'
  argv: [--exclude]
  argv: [*.html]            <- 同样的写法,直接写就是对的

旧写法让 aws 收到的模式是 "news/*"(带着引号),没有哪个对象的键是以引号开头的,所以排除形同虚设。

原因是:直接写在 eval 行上的文本,会被解析两次 ,先被调用时的 shell 解析一次,eval 再解析一次;而放进变量的字符串,展开后只剩 eval 这一次。写脚本的人把"直接写时需要的双层引号"原样抄进了变量,层数对不上。

为什么只删了两个文件,没把 957 篇文章一起删掉

看起来是这样:同一条命令里还有另一组排除规则 *.html、*.xml、*.txt,写法正确。文章是 .html,被它碰巧保护了,只有 .json 没人保护。AWS 文档也说过这条规则:

"files excluded by filters are excluded from deletion"

我要强调"看起来":我们没做"去掉那些规则会删多少"的实验,这只是对部署输出的解释,不是定论。

它验证过,为什么还是错了

这是我最没想到的地方。

我们都防着"没验证就动手"的错误。但这次 agent 验证了 ,验证的结果还是干净的。问题是它验证的,是它手敲的那条命令,不是脚本里的那条 。彩排验证了它的意图 ,没验证代码。

而且我的感觉是:"验证过"这三个字,反而比"没验证"更容易让人放松警惕。(这是我的感受,不是原文或实验结论。)

怎么避免

  1. 彩排要走脚本自己的代码路径。 给脚本加一个 --dry-run 开关,而不是在终端里手敲一条"我以为它会执行的命令"。
  2. 对彩排的输出做断言。 我们落地的做法是:真同步之前,先用脚本自己的参数彩排一遍,只要计划里出现删除 news/ 或 _pv/ 下对象,就中止部署。这两处是别的程序写的,本目录里永远没有。
  3. 别只靠 --exclude 的正确性兜底。 由别的程序写入的前缀,要有"计划里不得出现"这种独立的第二道防线。
  4. 给对象存储开版本控制。 AWS 文档说,开了版本控制之后,删除一个对象时,S3 会插入一个删除标记,而不是永久删除:

"if you delete an object, Amazon S3 inserts a delete marker instead of removing the object permanently."

这次如果开着,恢复就是一条命令,而不是从 957 个页面里反推。同一页还说,版本控制默认是关的,要自己打开;开了之后,每个版本都按正常价格计费(原文:"Normal Amazon S3 rates apply for every version of an object stored and transferred."),所以别忘了配合生命周期规则。

脚本里我还留了一段注释,写明这一行为什么只能用单层引号,免得以后有人"好心"把引号改回去。

参考与出处

  • 事实底稿:作者 2026-10-13 的操作记录与本机复现(printf 对比输出为 bash 5.3 实测)。
  • AWS CLI aws s3 sync 文档,docs.aws.amazon.com/cli/latest/... (取得日期 2026-10-13)
  • AWS CLI 过滤规则说明("Use of Exclude and Include Filters"),docs.aws.amazon.com/cli/latest/... (取得日期 2026-10-13)
  • Amazon S3 版本控制,docs.aws.amazon.com/AmazonS3/la... (取得日期 2026-10-13)
  • AWS 文档的几句英文引用均来自 2026-10-13 的网页抓取结果,与页面原句一致。
相关推荐
BreezeJiang7 小时前
RAG 答不准,先别急着换模型:给检索管道加上路由、拆解和循环检索
agent
麦客奥德彪8 小时前
别把 Agent 写成一个大循环:读懂 DeepSeek Harness 的接口与运行时设计
agent·源码阅读
hpoenixf8 小时前
为什么我们把 Agent 的路由请求越做越简单?
agent
布列瑟农的星空8 小时前
2026年我们不再谈论运行时Harness
agent
武子康9 小时前
ESP32 唤醒成功,离线命令为什么没有执行?
人工智能·后端·agent
易知微EasyV数据可视化9 小时前
会插卡片的编辑器:在 Agent 输入框里混排文字与结构化内容
人工智能·经验分享·编辑器·agent·设计
小七-七牛开发者10 小时前
从 AWS 到 OpenRouter,拆解 Agent 模型路由的工程设计
llm·agent·aws·openrouter·codingagent
阿阿阿安10 小时前
Agent 智能体开发(三)Agent 自定义框架搭建
人工智能·ai·agent
怕浪猫11 小时前
腾讯又交作业了,开源版 WorkBuddy 太夯了!这回是只粉色小章鱼Octop!
javascript·面试·agent