![]()
![]()
2026 年 7 月 12 日,一名開發者發文稱,Claude Code 在檢查數據庫遷移問題時,誤將生產數據庫當成臨時測試數據庫使用,導致生產數據庫的全部表結構被刪除并重新創建。
![]()
更令人意外的是,Claude Code 完成操作后并未發現異常,反而報告稱生產環境一切正常,“沒有進行任何修改”。
根據當事人的說法,Claude Code 最初正在排查測試環境中的數據庫遷移記錄與實際結構不一致問題。隨后,開發者追問生產環境是否也受到了影響,希望 Claude Code 只對生產數據庫進行檢查。
Claude Code 隨即運行 Prisma 數據庫遷移工具的 migrate diff 命令,用于比較遷移文件與生產數據庫結構之間是否存在差異。
問題出在命令參數上。
Claude Code 將生產數據庫地址同時設置為待檢查數據庫和“影子數據庫”。
影子數據庫是 Prisma 用于臨時重放遷移記錄的工作空間,系統默認其內部數據可以隨時清除,并不適合存放真實業務數據。
Prisma 官方文檔明確警告,實際數據庫地址與影子數據庫地址不得使用相同配置,否則可能刪除數據庫中的全部數據。影子數據庫在運行過程中會被重置,并重新執行已有的遷移記錄。
由于生產數據庫被錯誤指定為影子數據庫,Prisma 刪除了原有表結構,并重新執行了 116 個遷移文件。數據庫的表和字段雖然重新建立,但原有業務數據已不在這些新建的空表中。
隨后,Prisma 返回“沒有需要生成的遷移”結果。
Claude Code 將這一結果理解為生產數據庫與遷移記錄完全一致,因此判斷數據庫狀態正常。實際上,兩者之所以完全一致,是因為生產數據庫剛剛按照這些遷移文件被重新創建。
Claude Code 最終向開發者報告稱:“生產環境完全正常,沒有進行任何修改,所有操作均為只讀。”
當事人隨后檢查了約 2000 份 Claude Code 本地會話記錄,找到執行相關命令的會話。命令運行時間與數據庫中第一條重新生成的用戶記錄相差約 72 秒,由此確認了數據庫被重建的時間。
幸運的是,該公司使用的托管 PostgreSQL 數據庫支持按時間點恢復。開發者最終將數據庫恢復至事故發生約 2.5 小時前的狀態,全部數據得以找回,未造成永久性損失。
原貼翻譯:
我發出這篇帖子,是因為這種故障方式非常隱蔽。
我認為,即使換成人類開發者,或使用其他 AI 編程工具,也有不少人可能會踩中同一個坑。
事情的背景是這樣的,當時,我讓一個 AI 編程代理 Claude Code 排查測試環境中的數據庫遷移偏移問題。
隨后,我又追問了一句:“生產環境也受到影響了嗎?”
于是,它開始檢查生產環境是否正常。而生產數據庫恰恰是我明確不想觸碰的唯一一個數據庫。
最終摧毀數據庫的是下面這條命令:
prisma migrate diff \
--from-migrations ./prisma/migrations \
--to-url "$PROD" \
--shadow-database-url "$PROD" # <-- 這里指向的是生產數據庫
不了解 Prisma 工作機制的人,可能很難第一眼看出這條命令為什么如此致命。
當 prisma migrate diff 使用 --from-migrations 參數時,Prisma 必須找一個數據庫,實際執行這些遷移文件,從而計算遷移完成后的數據庫結構。
這個數據庫就是 --shadow-database-url 指定的“影子數據庫”。
Prisma 默認認為影子數據庫只是一個可以隨時丟棄的臨時工作區,因此會刪除其中原有的數據庫結構,再重新執行全部遷移。
Claude Code 將生產數據庫地址傳給了影子數據庫參數,相當于告訴 Prisma:將生產環境作為臨時工作區使用。
于是,Prisma 刪除了生產數據庫的全部表結構,并重新執行了 116 個遷移文件,最終只留下了一批沒有業務數據的空表。
最諷刺的是,比較結果隨后顯示:
This is an empty migration.
也就是“這是一個空遷移”,沒有發現任何結構差異。
Claude Code 將其理解為:“生產數據庫與最新遷移版本完全一致,運行正常。”
但結果之所以沒有差異,是因為生產數據庫就在一秒鐘前,剛剛按照這些遷移文件被完整刪除并重新創建。
Claude Code 最后的回復甚至明確寫道:
生產環境完全正常。沒有進行任何修改,所有操作均為只讀。
事情為什么會發生
坦率地說,原因有以下幾點。
第一,“只讀”并不是由 migrate diff 這個子命令本身決定的,而是由它搭配的參數決定的。
例如:
migrate diff --from-url ... --to-schema-datamodel
確實是只讀操作。
但是:
migrate diff --from-migrations ... --shadow-database-url
并不是只讀操作。
兩者使用的是同一個 migrate diff 子命令,但實際行為完全不同。
第二,Claude Code 在幾分鐘前剛剛對測試環境運行過同樣的命令,而且表面上看起來“成功了”。
實際上,它當時也悄悄清空了測試數據庫。
但測試數據庫本來就是一個正在重新構建、狀態已經異常的副本,所以這次重置沒有被人發現。
第三,生產數據庫所有者賬號的連接字符串,就放在一個臨時 Git 工作目錄的 .env 文件中。
它與一次常規命令之間,只隔著一個環境變量。
如何查明事故原因:
Claude Code 會將每一次會話以 JSONL 格式保存在本地磁盤中。
我在大約 2000 份會話記錄中搜索具有破壞性的命令特征,最終找到了相關會話。
致命命令的執行時間,與數據庫中第一條“重新生成的新用戶”記錄的出現時間,相差不到 72 秒。
兩份相互獨立的證據指向了同一個時間點,因此基本鎖定了事故原因。
有些荒誕的是,這次數據庫清空事件,最后是通過讀取 AI 自己留下的操作記錄還原出來的。
數據如何恢復:
我們使用的是支持按時間點恢復的托管 PostgreSQL 數據庫。
最終,我們將數據庫恢復到了事故發生約 2.5 小時前的狀態。
數據全部恢復,沒有永久丟失。
這也是我的公司今天還能繼續存在的全部原因。
這次事故的教訓:
--shadow-database-url 指向的數據庫,本質上就是一個你已經授權工具隨時刪除的數據庫。
絕對不要將它指向任何真實數據庫。
在 Prisma 的遷移工具中,只有 migrate deploy 適合在生產環境中使用。
db push、migrate dev,以及使用 --from-migrations 參數的 migrate diff,都可能執行重置或刪除操作。
此外,執行得更徹底,并不等于更安全。
Claude Code 選擇了最嚴格、最權威的驗證方式,但恰恰是這種驗證方式帶來了副作用。
因為在數據庫工具中,所謂“權威驗證”通常意味著,工具需要在某個地方真正創建和還原數據庫狀態。
真正的系統性解決方案,并不是“少相信 AI”。
關鍵在于,不要讓生產環境的可寫憑據出現在開發人員或 AI 代理可以直接訪問的終端環境中。
AI 的自主性只會放大現有權限控制的效果:你設置了哪些安全邊界,它就會放大哪些安全邊界;你沒有設置哪些邊界,它也會放大這些缺口。
即使沒有 AI,同樣的權限隔離措施,也能攔住一名晚上 10 點半因疲勞而誤操作的開發者。
歡迎提問。
有需要的話,我也可以公布一份完整的事故分析,包括取證證據,例如 pg_class.reltuples = -1、_prisma_migrations 記錄表消失等細節。
云頭條聲明:如以上內容有誤或侵犯到你公司、機構、單位或個人權益,請聯系我們說明理由,我們會配合,無條件刪除處理。
![]()
![]()
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.