整個資料集裡 bug 率最高的版本, 是 v3.4.1——59 個 bug、9 個 commit、39.39 sev/10c, 全部出自人類之手。 沒有人為它寫過一篇憤怒的 Mastodon 貼文。
Claude 真的讓 rsync 變多 bug 了嗎——用數據翻案一場社群怒火
2026 年 5 月底, 一則 Mastodon 貼文點燃了一場相當典型的開源社群怒火。 指控的內容很簡單: rsync——那個跑在無數備份腳本、 CI pipeline、 跨機房同步任務裡, 被信任了二十多年的工具——的維護者開始用 Claude 協助開發, 而新版本「明顯」變得 bug 變多了。 GitHub issue #929 在 5 月 30 日被開了出來, 留言區迅速堆滿了「AI 寫的程式碼就是不可信」「這就是 vibe coding 毀掉嚴肅軟體的範例」這類斷言。 情緒是真的, 截圖是真的, 連 bug 報告本身也大多是真的。
唯一沒有人做的事情, 是把「Claude 讓 rsync 變糟」這句話當成一個可以被量化、 可以被否證的假設來對待。 一位署名 Alexis Purslane 的研究者決定補上這一步: 把 rsync 從 v2.4.6 到 v3.4.3 共 36 個版本的 bug 全部翻出來, 用一致的標準量化每個版本的「嚴重度加權 bug 密度」, 然後問一個冷靜的問題——含有 Claude commit 的版本, 真的在統計上比歷史版本更糟嗎?
結論先講:證據不支持那則指控。 但這篇文章值得讀的地方不在結論, 而在它逐一檢驗假設、 攤開反例、 最後指出真正混淆變項的方法。 這正是一場 investigation 該有的樣子——我們以為是 X, 逐一測試, 發現不是 X, 然後找到真正的 Y。
對一個工程團隊而言, 這場爭議的重點不在 rsync 本身。 rsync 不會明天就崩, 它的維護者也不需要陌生人的辯護。 真正值得帶走的, 是一套面對「自從導入某工具, 品質就下降了」這類斷言時的紀律。 這種斷言在 2026 年會越來越常見——團隊裡只要有人開始用 LLM 寫 code, 遲早會有人把某次 incident 歸咎於它。 當那一刻到來, 你需要的不是立場, 而是把模糊的「感覺變糟」翻譯成一個能被資料反駁的命題的能力。 這篇 rsync 分析,正好示範了整個翻譯過程。
所以接下來我們不照貼文的情緒走, 而照調查的骨架走:先定義度量, 再把「Claude 讓事情變糟」拆成兩個可以分別否證的假設, 逐一拿資料去打, 最後再回答那個真正該問的問題——既然 bug 不是 Claude 帶來的, 它們到底從哪來。
先把「變糟」變成一個可以量化的數字
怒火裡所有的「明顯變多」「感覺更不穩」都是無法否證的。 要把它變成假設,第一步是定義一個度量。 文章採用的是每 10 個 commit 的嚴重度加權 bug 數,簡寫 sev/10c:
sev/10c = ( Σ(severity ÷ 100) ÷ total_commits ) × 10
// severity 是每個 bug 的 0–100 分嚴重度
// 除以 commit 數,是為了讓「改得多的版本」與「改得少的版本」可比
// ×10 只是把數字放大到好讀的尺度
這裡有兩個刻意的設計。 第一,bug 不是用「個數」算, 而是用嚴重度加權——一個會造成資料損毀的 bug, 跟一個 typo 在文件裡的 bug, 不該各算一票。 第二,分母是 commit 數而非時間, 因為一個版本改了多少東西, 直接決定了它有多少機會引進 bug; 不正規化的話,改動量大的版本天生吃虧。
嚴重度本身怎麼定?這是整個方法論裡最容易被質疑、 也最被誠實處理的一環。 每個 bug 由 Qwen 3 35B(temperature 0, 要求結構化 JSON 輸出)依一份明確 rubric 評分。 用一個 LLM 來評另一場關於 LLM 的爭議, 聽起來像是搬石頭砸自己的腳, 但 rubric 的閾值是固定的、 可稽核的:
90–100 資料損毀 / corruption;RCE 等級漏洞
70–89 crash / hang / 備份壞掉
50–69 功能回歸;效能問題
30–49 有 workaround 的小回歸
10–29 外觀問題;文件錯誤
0–9 spam;離題內容
這份 rubric 的關鍵不在於它「正確」——嚴重度分級永遠帶主觀——而在於它對每一個版本一視同仁。 同一個評分器、同一組閾值、同一個 temperature 0 設定, 套在 v2.4.6 的古老 bug 上, 也套在 v3.4.3 的最新 bug 上。 怒火的問題正是缺了這一層一致性: 人類在判斷「這個版本糟不糟」時, 會被「它有沒有 Claude 標籤」這件事污染, 而一個只看 bug 描述文字、 看不到 commit 作者的評分器不會。
bug 的來源涵蓋三處:GitHub REST API 的 issues、rsync 自己的 Bugzilla 實例、以及 rsync mailing list。 把三個來源合併, 是為了不讓「社群在哪裡吵」這件事偏倚資料——如果只看 GitHub, 自然會撈到最多最近、最被 AI 議題吸引來的 issue。 Bugzilla 與 mailing list 是 rsync 二十年來的傳統回報管道, 把它們納進來, 等於把時間軸往前拉到 Claude 出現之前很久, 讓「歷史基準」這件事有真正的厚度, 而不是只拿最近幾個版本當對照。
還有一個容易被忽略的設計細節:什麼算「Claude commit」。 這個分析採用的是 git 歷史裡有明確 Co-authored-by 或 commit message 標記 Claude 參與的提交。 這個界線是保守的——它只認那些被誠實標注的, 不去猜測哪些「看起來像 AI 寫的」。 保守界線的好處是, 它不會把人類獨力完成、 只是風格剛好相近的 commit 誤算進來, 從而人為製造 Claude 與 bug 的關聯。 代價是可能漏算一些未標注的 AI 協助, 但這個方向同樣對「Claude 更糟」的假設不利: 真正的 Claude 參與只會比測到的更多更廣。
下面這張圖把 36 個版本的 sev/10c 全部攤開。 在你讀任何敘事之前, 先讓資料的形狀說話: 兩個含 Claude commit 的版本(標為 accent 色), 跟那一根突兀的、純人類的最高峰, 分別落在哪裡?
drag the threshold to count versions at-or-above any sev/10c · 36 versions
36 個 rsync 版本(v2.4.6 → v3.4.3)的 sev/10c
36 個版本裡 bug 率最高的 v3.4.1(39.39 sev/10c)是純人類版本,兩個 Claude 版本都不突出。
把門檻拖到 v3.4.3 的 3.29, 你會看到: 在 36 個版本裡, 有相當一批版本同等或更糟。 把門檻拖到 v3.4.2 的 0.00, 整個資料集都「不比它好」——因為它根本沒有 bug。 而那根衝到 39.39 的長條, 孤零零地站在所有東西之上, 標籤寫著 v3.4.1, 是一個純人類版本。 形狀已經先給了答案的輪廓: Claude 版本沒有特別突出。
這張圖還透露出一件關於 rsync 本身的事, 值得順手記下。 sev/10c 在歷史上本來就高度波動——有 v3.1.0、 v3.2.0、 v3.3.0、 v3.4.0 這種跟著大版本來的高峰, 也有一整排貼著 IQR 下緣的乾淨版本。 一個成熟、 被廣泛使用的系統工具, 它的 bug 率天生就是鋸齒狀的: 大改動的版本高、 收尾穩定的版本低。 在這種背景噪音裡, 要主張「某兩個版本異常」, 你得先證明它們跳出了這條歷史鋸齒的正常擺幅。 而 v3.4.3 的 3.29 還落在 v3.3.0、 v3.0.0 這些純人類版本之間, 根本沒有跳出去。 換句話說, 怒火指控的「異常」, 連從背景噪音裡浮出來都做不到。
假設一:含 Claude commit 的版本,平均 bug 更嚴重
這是怒火的核心命題。 要測它, 先把兩個 Claude 版本的真實數字攤開:
- v3.4.2: 50 個 commit,其中 9 個是 Claude commit, 0 個 bug,0.00 sev/10c——落在第 0 百分位。
- v3.4.3: 34 個 commit,其中 28 個是 Claude commit, 17 個 bug,3.29 sev/10c——落在第 77 百分位。
這兩個版本剛好分落 IQR 的兩端: 中段 50% 的版本介於 0.29 到 2.59 sev/10c 之間, v3.4.2 卡在下緣之下, v3.4.3 卡在上緣之上。 兩個樣本, 一個是資料集裡最乾淨的版本之一, 一個略高於上四分位。 光憑這個對比, 你已經能感覺到「Claude 版本」並不是一個內聚的群體——它的兩個成員幾乎站在分佈的對角。
這個「分落兩端」的細節值得停一下。 如果 Claude 真的對程式碼品質有系統性的影響——不論好壞——你會預期兩個 Claude 版本至少落在分佈的同一側。 一個沉到 0、 一個浮到第 77 百分位, 這種離散正是「沒有共同因子」的特徵。 把 Claude 當成解釋變項, 它解釋不了這兩個版本之間的巨大差距; 能解釋的, 是每個版本各自改了什麼、 被什麼壓力推著改。 換句話說, 「是不是 Claude 版本」這個標籤, 對預測 bug 率幾乎沒有資訊量。
但「感覺」不是統計。 文章用的主力工具是 exact permutation test——枚舉所有可能的版本配對, 問一個非常具體的問題: 如果我從歷史版本裡隨手抽兩個, 它們的平均嚴重度「同等或更糟於這兩個 Claude 版本」的機率有多高?這個機率就是 p-value。 它不需要任何分佈假設, 因為它直接窮舉了全部可能性。 下面這個 widget 讓你親手抽。
drag the resample count to watch a Monte-Carlo permutation sampler converge toward the article's exact p ≈ 46%
從歷史版本隨機抽兩個, 計算其平均 sev/10c 的分佈
從歷史版本隨機抽兩個,有 46% 機率同等或更糟——Claude 版本平均達不到統計顯著差異。
抽得越多, 右側(accent 色, 同等或更糟)的比例就越穩定地收斂——文章給出的 exact 答案是 p = 46%, 單側檢定, 對立假設是「Claude 平均 > 歷史平均」。 換句話說: 隨手從 rsync 歷史抽兩個版本, 有將近一半的機率, 它們同等糟或更糟。 一個 p = 46% 的結果, 連最寬鬆的顯著性門檻(0.05、 甚至 0.1)都遠遠搆不到。 從這個角度看, 「Claude 版本更糟」這個假設, 沒有得到資料支持。
這裡要對「exact」這個字眼較真一下, 因為它是這個檢定力量的來源。 一般我們跑 permutation test 會用 Monte Carlo 取樣——隨機抽幾千次來逼近 p-value, 就像上面這個 widget 做的那樣, 抽越多越準。 但當樣本夠小(這裡是從 36 個版本裡選 2 個當「Claude 組」), 所有可能的分組是可以完全枚舉的。 把全部配對都算過一遍, 得到的 p-value 不是估計值, 而是精確值——沒有取樣誤差, 沒有「再跑一次會不會不一樣」的疑慮。 widget 用 Monte Carlo 讓你「感覺」到收斂的過程, 但文章報的 46% 是把那條收斂曲線一路推到底的終點。
為什麼選 permutation test 而不是更熟悉的 t-test?因為 t-test 假設資料近似常態分佈, 而 sev/10c 顯然不是——它有一條長尾, 還有一個衝到 39.39 的極端離群值。 permutation test 不對分佈做任何假設, 它只問「在所有可能的標籤重排下, 我觀察到的這個分組有多極端」。 對一個 n = 2 對 34 的小樣本、 又帶離群值的資料集, 這是更誠實的選擇。 它的代價是統計檢定力(power)低——但在這裡, 連檢定力低的測試都測不出顯著差異, 這個結論反而更穩固。
文章還用了第二個工具當交叉驗證: Fisher exact test, 把版本二元化為「有 bug/無 bug」之後檢定關聯性, 得到 p = 74%, odds ratio 1.06。 odds ratio 1.06 的意思是, 「是 Claude 版本」與「出 bug」之間的關聯, 比擲硬幣強不了多少。 兩個獨立的檢定, 從兩個不同的角度切——一個看連續的嚴重度、 一個看二元的有無——指向同一個方向: 沒有訊號。 當兩種方法論互不相干卻得到一致的「查無此事」, 這個結論比任何單一 p-value 都更難被推翻。
假設二:那也許是樣本太小,被一個好版本稀釋了
一個合理的反駁是: 你只有兩個 Claude 版本, 而其中 v3.4.2 乾淨得異常(0 bug), 會不會是它把平均拉下來、 掩蓋了 v3.4.3 的問題?這是個誠實的質疑, 文章自己也把「只有 2 個 Claude 版本」列為頭號限制。 但要回答它, 最有力的不是更多統計, 而是一個反例。
反例就是 v3.4.1。 它是純人類版本——零 AI 參與——卻是整個資料集裡 bug 率最高的版本: 9 個 commit 擠進了 59 個 bug, 39.39 sev/10c, 落在第 97 百分位。 這個比例有多誇張?平均每個 commit 帶進超過 6 個 bug, 而它的 sev/10c 是次高版本(v3.4.0 的 9.10)的四倍多, 是 IQR 上緣的十五倍。 它不只是分佈裡的一個高點, 它是一個讓 y 軸尺度被它一個人撐開的離群峰。
如果社群的判準真的是「bug 多就該被罵」, 那 v3.4.1 才是頭號嫌疑犯, 而且差距大到沒有第二名。 但翻遍 issue tracker, 沒有人為它寫過一篇憤怒貼文, 沒有「人類維護者讓 rsync 變不可信」的串。 差別只有一個:v3.4.1 沒有「Claude」這個可以歸咎的標籤。 這正是歸因偏誤的教科書形態——人們不是對「bug」反應, 而是對「可歸咎的對象」反應; 當對象不存在, 同等嚴重的 bug 就靜悄悄地被修掉、 被遺忘。 下面這張可排序的表把關鍵版本並排——點任一欄標題排序, 看看誰才真的在頂端。
click a column header to sort · 6 columns × 6 rows
| version | commits | claude commits | bugs | sev/10c | percentile |
|---|---|---|---|---|---|
| 3.4.1 | 9 | 0 | 59 | 39.39 | 97 |
| 3.4.0 | n/a | 0 | n/a | 9.10 | 90 |
| 3.2.0 | n/a | 0 | n/a | 7.95 | 85 |
| 3.4.3 | 34 | 28 | 17 | 3.29 | 77 |
| 3.0.0 | n/a | 0 | n/a | 4.85 | 80 |
| 3.4.2 | 50 | 9 | 0 | 0.00 | 0 |
關鍵版本的 commit、Claude commit、bug 與 sev/10c
排序後最高的是 v3.4.1(純人類,39.39),兩個 Claude 版本分落第 0 與第 77 百分位。
表裡還藏著另一個反直覺的事實。 Claude 版本改動的程式碼量遠遠大於歷史平均: 平均 3,756 行 vs 歷史的 696 行(p = 5%, 這個差異是真的顯著)。 直覺上, 改得越多越容易出 bug——這幾乎是軟體工程裡最穩的經驗法則之一, diff 越大、 回歸風險越高。 但 Claude 版本的嚴重度加權 bug 反而更少——平均 5.6 vs 歷史 14.9(p = 77%, 這個差異不顯著,但方向至少不是變糟)。 改了五倍多的程式碼, bug 沒有等比例增加, 這恰恰是「Claude 讓品質下降」假設的反面。
把這兩個數字放在一起讀, 會逼出一個有趣的推論。 如果你天真地以「每行改動帶來多少 bug」來衡量, Claude 版本的單位 bug 密度其實低於歷史。 當然, n = 2 不足以把這個推論當定論——這正是為什麼那個 p = 77% 不顯著、 文章也不去吹捧它。 但它至少封死了一條退路: 你不能一邊說「Claude 版本 bug 不算少」, 一邊忽略它們扛下的改動量是歷史的五倍。 把規模納入考量, 「Claude 讓事情變糟」的假設不只沒被支持, 連符號都站錯邊了。
所以假設二也站不住: v3.4.2 的乾淨不是稀釋了一場災難, 因為 v3.4.3 本身的 3.29 在 36 個版本裡也只是「略高於上四分位」, 遠不到 v3.4.1 那種離群等級。 樣本小是真的、 不確定性是真的, 但即使把放大鏡對準那個「更糟」的 Claude 版本, 它也沒有特別糟。
真正的混淆變項:不是誰寫的,是改了多少
到這裡, 兩個假設都被否證了: Claude 版本沒有更糟(permutation p = 46%、 Fisher p = 74%), 小樣本也不是在掩蓋災難(反例 v3.4.1 與五倍改動量都指向反方向)。 但 v3.4.3 確實有 17 個 bug,這個數字是真的。 那這些 bug 從哪來?
答案是一條混淆變項的因果鏈, 而它跟 Claude 的程式碼品質無關。 文章引述維護者確認的版本是這樣的: 2025 年起, AI 生成的 CVE 報告開始灌爆 rsync——大量由 LLM 自動產出、 品質參差的安全性報告, 逼著維護者對 rsync 的攻擊面做大量、 快速的改動。 更多安全性導向的改動, 意味著更多觸碰核心邏輯的機會, 也就意味著更多回歸(regression)。 v3.4.3 之所以同時是「Claude 參與最深」和「bug 較多」的版本, 是因為它正好是那波 CVE 風暴下被迫大改的版本——Claude 是被叫來幫忙救火的工具, 不是縱火犯。
這條鏈解釋了前面那個「改動量是歷史五倍」的怪數字。 一個維護者不會無緣無故在一個 patch release 裡塞進 3,756 行改動; 那種規模的 diff 背後一定有外部壓力。 當安全性報告以遠超人力負荷的速度湧進——其中很多是 LLM 幻覺出來的假漏洞、 或把無害行為誤判成攻擊面——維護者被迫在短時間內對協定解析、 權限檢查、 緩衝邊界這些最脆弱的區域動刀。 這些區域恰好也是最容易在修一個問題時碰壞另一個問題的地方。 回歸不是因為改的人不行, 而是因為改的位置危險、 改的節奏被外力逼快了。
這就是混淆變項的標準定義: 一個同時影響「假定的因」和「觀察到的果」的第三變項。 在這裡, 「CVE 風暴」同時推高了「Claude 的參與度」(需要大量改動, 於是把 Claude 叫來幫忙)和「bug 數」(大量危險區域的改動帶來回歸)。 如果你只盯著 Claude 和 bug 這兩端, 會看到一條虛假的相關; 把第三變項放進來, 相關就被解釋掉了。 這是統計入門課裡 confounding 的活教材, 只是這次它穿著一件「AI 讓軟體變糟」的外衣登場。
同一個事實——「v3.4.3 有 17 個 bug」——可以被讀成兩條完全不同的因果鏈。 把下面的分隔線從左拖到右, 從怒火的版本切換到資料支持的版本。
drag the divider · 怒火的因果鏈 ↔ 資料支持的因果鏈
互動圖表
v3.4.3 的 17 個 bug 源自 AI 生成的 CVE 報告灌爆專案、逼出大量改動,與 Claude 程式碼品質無關。
兩條鏈的終點是同一個數字, 起點卻是兩種完全不同的世界觀。 怒火的讀法把「Claude」放在因果的源頭, 於是任何 bug 都成了它的罪證——這是一種把工具當代罪羔羊的歸因偏誤。 資料支持的讀法把源頭放在「AI 生成的 CVE 報告灌爆專案」這個外部壓力上: 是它逼出了大量改動, 而大量改動天生帶來回歸, 無論這些改動是誰、用什麼工具寫的。 諷刺的是, 這兩條鏈裡都有「AI」——只是真正製造麻煩的那個 AI, 是那些自動產出垃圾 CVE 報告的 LLM, 不是寫修補程式的 Claude。
這條混淆變項的鏈, 對任何在 2026 年帶團隊的人都不只是 rsync 的趣聞。 LLM 自動產出的安全性報告正在以前所未有的量級湧入各種專案的 issue tracker——curl 的維護者公開抱怨過、 許多大型 OSS 專案都在處理同一波垃圾。 這意味著「被 AI 灌爆」與「開始用 AI 幫忙消化」這兩件事, 會在很多專案裡同時發生、 彼此糾纏。 於是你會在資料裡看到一個假相關: 用了 AI 的版本好像 bug 變多。 但真正的驅動力是上游那條看不見的壓力——報告量暴增逼出改動量暴增, 而改動量才是回歸的真正母體。 下次有人拿「導入 AI 後 bug 變多」來下結論時, 第一個該問的不是「AI 寫得好不好」, 而是「這段期間我們被迫改了多少、 為什麼被迫改」。
這篇分析自己承認的不確定性
一篇誠實的 investigation 不會假裝自己證明了反命題。 文章很清楚地把限制攤在桌面上, 這也是它值得被當成範本的原因。
第一,樣本極小:只有 2 個含 Claude commit 的版本。 任何基於 n = 2 的結論都必須謙卑。 p = 46% 並不證明「Claude 讓品質更好」, 它只證明「資料無法支持 Claude 讓品質更糟」——這是一個關於「沒有訊號」的陳述, 不是一個關於「有反向訊號」的陳述。 缺乏證據不等於證據缺乏, 但在這場怒火裡, 主張方是社群, 舉證責任在他們, 而資料沒有給他們任何彈藥。
第二,嚴重度評分依賴一個 LLM。 用 Qwen 3 35B 依固定 rubric 評分, 比起人類主觀打分更一致、 更可重現, 但它仍然是一層需要被信任的中介。 文章的緩解措施是公開 rubric 閾值、 用 temperature 0 與結構化輸出讓評分可稽核——但如果你不信任這個評分器, 整個 sev/10c 度量都會跟著鬆動。 這是誠實的揭露, 不是可以揮手帶過的細節。
不過這層中介的影響方向值得想清楚, 因為它不是對稱的。 評分器看的是 bug 的文字描述, 它完全不知道某個 bug 來自哪個版本、 那個版本有沒有 Claude 參與。 也就是說, 即使 Qwen 3 35B 的絕對分數有系統性偏差——比方說它傾向把所有 crash 類 bug 都打高一兩分——這個偏差會均勻地灑在所有 36 個版本上, 因此在「Claude 版本 vs 歷史版本」這個比較裡會大致抵消。 會傷害結論的只有一種偏差: 評分器恰好對「Claude 版本的 bug」系統性打分不同。 但評分器看不到版本標籤, 這種偏差沒有發生的管道。 所以「依賴一個 LLM」是真的限制, 卻不是會翻轉這場比較方向的那種限制。
第三,bug 來源可能有取樣偏倚。 把 GitHub、Bugzilla、mailing list 合併是為了平衡, 但「哪些 bug 被回報」本身就受社群注意力影響——當一個版本因為 AI 議題被放大檢視時, 它的 bug 自然更容易被翻出來、被歸檔。 這個方向反而對 Claude 版本不利(它們被盯得最緊), 所以如果有偏倚, 真實的 Claude bug 率可能比測到的還低。
把這三點放在一起, 文章的姿態是一致的: 它不宣稱 Claude 讓 rsync 變好, 只拒絕在沒有證據的情況下宣稱它變糟。 permutation test 的 46%、 Fisher 的 74%、 odds ratio 1.06、 反例 v3.4.1 的 39.39、 五倍改動量卻更少 bug——這些數字加起來, 足以讓那場怒火失去它的事實基礎, 但不足以、也不該被用來反過來吹捧 AI。
Take-away: 下次看到「自從用了某工具, 東西就變糟了」這種因果斷言, 先別急著加入怒火——把「變糟」變成一個可量化、 可否證的度量, 找出資料集裡最糟的那個反例(它很可能不帶你想罵的標籤), 再問一句: 真正改變的混淆變項是什麼?在 rsync 這場戲裡, 答案是改動量,而改動量的源頭, 是另一群 AI 灌出來的垃圾 CVE 報告。