凌晨 1 点,我让 Codex 顺手清一下
node_modules。 8 分钟后,C 盘里我的 Documents、Desktop、Downloads 都被它从字母 A 扫到了 Z。 而且------是我亲手点的 Approve。
前言:一次"看起来无害"的清理
事情起因蠢得让人不想承认。
我在一个 Next.js 项目里折腾 pnpm,node_modules 已经膨胀到深路径无法用普通方式删除(Windows 下经典 路径过长 报错)。我开了个 Codex Desktop 会话,对 cwd 指着那个项目,发了一条 5 个字的中文 prompt------大意是"继续做一下"------然后看着它出方案。
方案出来挺漂亮:
- 用
\\?\长路径前缀绕开 Windows 的 260 字符限制 - 调
cmd /c "rd /s /q ..."强制无确认删除 - 还加了
StartsWithsafety check,确保删除目标在 root 之内
我看了一眼,点了 Approve。
然后命令开始跑,10 秒后超时退出。Codex 看输出报错,自己改写命令重试。又超时。再试。再试......
8 分钟后我才意识到:Codex 的输出里出现了 \$Recycle.Bin、\Boot\BCD、\$WINRE_BACKUP_PARTITION.MARKER 这些字符串。
这些不是我的项目目录里的文件------这是 C 盘根目录。
现场:从字母 A 删到了 Z
事后从 Codex 的 sqlite 日志里挖出来的命令实际输出长这样(节选):
erlang
command timed out after 120426 milliseconds
\$Recycle.Bin\S-1-5-18 - Access is denied.
\$Recycle.Bin\S-1-5-~1 - Access is denied.
\$Recycle.Bin - Access is denied.
\$WINRE~1\Rollback - Access is denied.
\$WINRE_BACKUP_PARTITION.MARKER - Access is denied.
\AMTAG.BIN - Access is denied.
\Boot\BCD - Access is denied.
\Boot\bg-BG\bootmgr.exe.mui - Access is denied.
\Boot\cs-CZ\bootmgr.exe.mui - Access is denied.
\Boot\da-DK\bootmgr.exe.mui - Access is denied.
\Boot\de-DE\bootmgr.exe.mui - Access is denied.
...
这是 rd /s /q 在 C 盘根目录按字母序遍历的输出。
- 系统目录(
$Recycle.Bin、Boot\BCD、$WINRE_BACKUP_PARTITION.MARKER)有 ACL 保护,被拒,所以系统还能启动; - 我自己的目录(Documents、Desktop、Downloads 等),ACL 是我自己授权的,全军覆没。
最后是 Windows 的**卷影副本(Shadow Copy)**救了我一命。
恢复过程:卷影副本是怎么救回来的
发现日志里冒出 \$Recycle.Bin 那一瞬间,我做的第一件事是关掉 Codex Desktop 进程 ------rd /s /q 是无回收站的硬删,删一个少一个,没办法 Ctrl+Z 回退。损失已经定格,剩下的就是把能捞的捞回来。
止损完,我开了 Claude Code 新会话求救。一句话:"cc 全完了,codex 把我 C 盘大量数据全删了"。
整个恢复花了大约 1 小时 28 分钟,关键步骤如下:
1. 先评估损失范围
bash
ls /c/Users/admin/
du -sh /c/Users/admin/Desktop /c/Users/admin/Documents /c/Users/admin/Downloads ...
确认 Desktop、Documents、Downloads、Pictures、.claude、.docker、.aws、.azure 这些都没了------大约 22GB 数据 。.claude 整个目录消失意味着我的 Claude Code 配置、credentials、MCP、skills、plugins 全部丢失,下一次启动就是裸奔。
2. 找有没有可用的卷影副本
powershell
vssadmin list shadows
Get-WmiObject -Class Win32_ShadowCopy | Select DeviceObject, InstallDate, VolumeName
幸运的是,系统上有三个快照 (5/1、5/11、5/18 各一个)。最近一个是 5/18 凌晨------离事故 5/23 凌晨整整 5 天。这意味着我会丢 5 天里在 C:\Users\admin\ 下产生的所有新文件,但 22GB 里的核心数据是能找回来的。
如果这一步输出"未发现任何卷影副本",那基本就没救了------这也是后面教训里反复强调"现在就去把系统保护打开"的原因。
3. 选定快照,挂成可访问的路径
VSS 卷影副本的原始路径长这样:\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy3\,对很多 Windows 工具不友好(Copy-Item 解析会出问题)。用 mklink 创建符号链接绕开:
cmd
cmd /c "mklink /D C:\ShadowCopy3 \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy3\"
坑点 1 :mklink 是 cmd 内置命令,不能直接在 PowerShell 里跑 ,必须 cmd /c 包一层。 坑点 2:源路径末尾的反斜杠不能省。
4. 用 robocopy 而不是 Copy-Item
我第一反应是 Copy-Item -Recurse -Force,结果深层子目录死活拷不全 (Copy-Item 在 VSS 路径下有玄学问题,可能跟它处理符号链接和 reparse point 的方式有关)。换 robocopy:
powershell
robocopy '\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy3\Users\admin\.claude\' `
'C:\Users\admin\.claude\' `
/E /COPY:DAT /R:1 /W:1 /NP
/E包含空目录在内的所有子目录/COPY:DAT拷数据+属性+时间戳(不要用/COPYALL,VSS 里 ACL 拷过来会很麻烦)/R:1 /W:1失败只重试 1 次、等待 1 秒,避免卡死/NP不打进度,日志干净
.claude、Documents、Desktop、Downloads、Pictures 几个关键目录全部用这个套路拉回来,11 分钟搞定核心数据。
5. 处理 git 仓库 + 受损文件
VSS 快照里包含 git 仓库时,.git/index 可能跟当前 working tree 状态不一致。.claude/plugins/marketplaces/ 下几个插件市场都是 git 克隆来的,恢复后 git 命令报错。直接重置:
bash
rm .git/index && git reset --hard HEAD
6. Chrome 数据的"玄学"恢复
这一段是最折腾的。Chrome 用户数据从 VSS 拷回来后,Chrome 启动直接报"配置已损坏"。具体踩了几个连环坑:
- SingletonLock / SingletonSocket:Chrome 的进程锁文件,VSS 里那一份是旧状态,必须删掉再启动
- LevelDB journal 不一致 :
*-journal和LOCK文件要全部清掉,LevelDB 才肯重建 - Secure Preferences 的 HMAC 校验 :Chrome 会用 HMAC 验配置完整性,从 VSS 拷过来 HMAC 对不上,得手动清空
super_mac字段让 Chrome 重算 Bookmarks文件含 null bytes :这个最离谱------三个快照里的 Bookmarks 文件读出来都掺着\x00,标准json.loads直接挂。试了esentutl /y(微软官方推荐的 VSS 复制工具)、试了 UTF-16LE 解码、试了 null byte 剥离 + 截断式 JSON 修复,都没能完全救回来
最后这步是从我另一台 Mac 上同步过来兜的底------好在浏览器书签和密码这种东西大多数人会有 Google 同步或者多端冗余。
7. 清理 + 验证
cmd
rmdir C:\ShadowCopy3
挨个验证:.claude 启动 OK、docker 服务能起、各 .aws/.azure/.docker 凭证可用、IDE 配置回来了。总耗时 88 分钟,没救回来的主要是 Chrome 的 Bookmarks 和事故前 5 天产生的新文件------能接受。
总结一句 :卷影副本是 Windows 给你免费的事故保险,但前提是你提前开了系统保护。如果你看到这里发现自己没开------放下文章,先去开。控制面板 → 系统 → 系统保护 → 选 C 盘 → 配置 → 启用系统保护,分配 5-10% 的磁盘空间。
顺便说一句:别迷信卷影副本能救一切 。VSS 是文件系统级快照,对运行中的程序(浏览器、数据库、IDE)当时打开的文件状态不一定一致,恢复后经常需要额外修补------参考上面 Chrome 那一节。重要数据的真正兜底是多端同步 + 异地备份。
真凶命令
事故 turn 的命令我从 ~/.codex/logs_2.sqlite 里完整还原出来了:
powershell
$root='C:\Users\admin\Documents\Codex\my-frontend';
$target=Join-Path $root 'node_modules';
if (!(Test-Path -LiteralPath $target)) { "TARGET_NOT_FOUND=$target"; exit 0 }
$resolvedRoot=(Resolve-Path -LiteralPath $root).Path;
$resolvedTarget=(Resolve-Path -LiteralPath $target).Path;
if (-not $resolvedTarget.StartsWith($resolvedRoot)) {
throw "Safety check failed: target outside root"
}
$longPath='\\?\' + $resolvedTarget;
cmd /c "attrib -R /S /D \"$resolvedTarget\*\"" | Out-Null;
cmd /c "rd /s /q \"$longPath\""; # <-- 真凶
读到这里很多人会皱眉:这命令一点毛病都没有啊?
- 有
Test-Path前置判断 ✅ - 有
Resolve-Path路径解析 ✅ - 有
StartsWithsafety check ✅ - 长路径前缀
\\?\也是标准做法 ✅
模型自己加了 4 道防线,逻辑上完全正确。但结果就是删了我半边 C 盘。
为什么会爆炸
罪魁祸首是最后那一行:
powershell
cmd /c "rd /s /q \"$longPath\"";
$longPath 实际是:
makefile
\\?\C:\Users\admin\Documents\Codex\my-frontend\node_modules
按设计,这应该传给 cmd.exe 让 rd 删指定目录。但 PowerShell 调外部命令时的 native argument passing 会重写引号和反斜杠 ------\\?\ 这个特殊前缀在 PowerShell→cmd 这一跳里被"消化"掉了一部分,cmd 收到的字符串和你写的不一样。
具体到这条命令上发生了什么我没在隔离 VM 里实测复现,但结果是确定的 :rd /s /q 拿到一个它解析不出的路径,退化为对当前盘根目录递归删除。
一句话:Agent 写的命令字面上没错,但 PowerShell 和 cmd 这俩 shell 之间传参时摔了一跤,掉进了 C 盘根。
Sandbox 为什么没拦住?
事故 turn 的 Codex sandbox 是这么配的:
yaml
sandbox_policy: WorkspaceWrite {
writable_roots: [
C:\Users\admin\Documents\Codex\my-frontend,
C:\Users\admin\Documents\Codex
],
network_access: false,
}
理论上,Codex 不应该允许命令写出 writable_roots 之外的路径。
但这条命令的真正执行者不是 PowerShell ,而是 PowerShell 启动的 cmd.exe 子进程,rd 又是 cmd 的内置命令------整个删除动作发生在 cmd.exe 进程里,绕过了 Codex 对 PowerShell 主进程的 syscall 拦截。
更直白点说:
只要 Agent 能起子进程,sandbox 就是个君子协议。
这不是 Codex 独有的问题。任何只在主进程层做拦截的 Agent sandbox(Claude Code、Cursor 的 agent mode、各家 IDE 内嵌 Agent)都吃这个亏------shell 套 shell,权限是穿透的。
"AI Coding 时代的盲信成本"
我反复想这件事到底蠢在哪里。
不是命令蠢------命令逻辑严密、还自带 safety check; 不是模型蠢------它选了一个工业界常用的"长路径 + 强删"组合; 不是 sandbox 蠢------WorkspaceWrite 该挡的都挡了,是 cmd 子进程绕的;
蠢的是我点 Approve 的那一秒钟脑子里在想什么。
我想的是:
"看起来挺合理的,cwd 在项目里、有 safety check、就是删个
node_modules,能出什么事?"
我没想的是:
cmd /c rd这个组合在我的具体 Windows 环境(PowerShell 5.1)上行为是否可预测?\\?\长路径前缀 + 引号 + 反斜杠转义这一串嵌套,跨 shell 传参的边界条件是什么?- 如果命令因为任何原因解析失败、回退、超时------它的失败模式是什么?
模型逻辑正确 ≠ 命令在你机器上行为正确。
AI Coding 给我们带来了一种很危险的错觉:Agent 既然能写出"看起来对的"代码 / 命令,那就一定能在我的环境里跑对。
这是错的。Agent 的训练数据里有的是"在干净 Ubuntu 容器里能跑通的 Bash";它没在我的 Windows 10 + PowerShell 5.1 + 当前 ACL 配置 + 当前长路径策略组合上跑过任何一次。
你点 Approve 那一下,是在拿你的实际环境给它的命令做生产验证。
教训(也是我从今天起的新规矩)
1. Approve 要看到具体命令,不能看"意图"
之前我看到"删除 node_modules"就放心了。从今天起,只要命令里出现下面任意一个东西,必须停下来读一遍命令本身:
rd /s /q/rm -rf/Remove-Item -Recurse -Forcecmd /c/bash -c/sh -c(跨 shell 嵌套)\\?\/ 任何路径里有变量插值chmod/icacls/takeown(权限变更)- 任何超过 3 行的 shell 脚本(短代码块还能审,长的基本只能赌)
2. Sandbox 不可信,OS 层 ACL 才是兜底
我的应对:
- 项目目录全部挪出系统盘 (我把所有项目从
C:\Users\admin\Documents\挪到了F:\盘根) - Agent 的 cwd 永远不要指向
C:\Users\xxx\下任何位置------一旦命令跑飞,整个用户目录都在 ACL 允许范围里 - 始终开 Windows 卷影副本(System Restore + File History),这次救命的就是它
3. Approval Policy 要选"每次 exec 都问",不要"按 turn 批准"
Codex 的 OnRequest 模式有个隐藏陷阱:我批准了第一条 exec,后面同一 turn 里的 retry 和后续命令是不再弹窗的。
事故 8 分钟里我只看到过 1 次 Approve 弹窗。剩下 3 次灾难命令在我不知情的情况下跑完了。
各家 Agent 的 approval 粒度不一样,但选择"最细粒度的问询"永远没错。
4. 给 AI 干活的目录要"小盒子"
不要让 Agent 直接对着一个"项目根目录"开火。新建一个一次性的工作目录,干完了删掉。
arduino
F:\agent-workspace\
├── task-2026-05-23-cleanup\ ← 这次任务的"沙箱"
│ └── node_modules-to-delete\ ← 把要操作的东西先 move 进来
└── task-2026-05-24-xxx\
干活前先 Move-Item 把要处理的东西挪进"沙箱目录",让 Agent 在沙箱里随便折腾。这是真正的"workspace isolation",不依赖 Agent 自己的 sandbox 实现。
立刻可以做的 3 件事
不管你用 Claude Code、Cursor、Codex、还是其它任何 Agent:
- 现在就把
approval_policy改成最严格的"每次 exec 都问" (各家配置不同,搜文档关键词auto-approve/permission/approval) - 检查 Windows 卷影副本是否启用:控制面板 → 系统 → 系统保护 → C 盘是否开启
- 把项目目录从
C:\Users\<你>\下挪到非系统盘------这一步成本最高但收益最大
写在最后
这篇文章不是黑 Codex。事故定位过程里我得说一句公道话:Codex 的内部日志(~/.codex/logs_2.sqlite)保留得非常完整,连 turn_id、thread_id、每个 shell exec 的完整 arguments 和 12000+ 行输出全都在,让事后追责成为可能。这篇文章本身,就是建立在这套日志之上写出来的。
我想说的是另一件事:
AI Coding 时代的'生产力',本质是把'决策密度'从'每一行代码'压缩到了'每一次 Approve'。
以前你要敲 100 行代码做一件事,每敲一行都在做小决策。现在你只点一次 Approve,背后那 100 行的全部风险被打包成一个按钮。
这个按钮值多少 token 的注意力------你愿意付,还是不愿意付,决定了你是 AI 时代的受益者还是受害者。
我这次付了一份"半个 C 盘"的学费。希望写出来,能让你少付一点。
事故还原全过程、
logs_2.sqlite提取脚本、可复用 SQL 查询,都在我个人仓库的事故归档里。如果你也踩过类似的坑,评论区聊聊------咱们一起把 AI Coding 时代的"地雷地图"画起来。
AI Coding Vibe Coding AI Agent Codex Windows 踩坑