要求只有一句:把檔案裡的 APPLE 換成 ORANGE,其他什麼都不要動。

Claude 照做了,Edit 沒有報錯。檔案原本 107 個位元組,改完變成 182 個。多出來的 75 個位元組,只有 1 個來自 ORANGE 比 APPLE 多一個字母,其餘來自檔案裡讀不懂的位元組被換成 U+FFFD,就是那個裡面有問號的菱形方框,issue 裡數到 38 個。連 Edit 根本沒碰的那幾行也一樣。

這是 anthropics/claude-code 的 issue #96263,2026 年 9 月 23 日(UTC)開的,回報版本 2.1.280。回報者的環境是 Windows 11 日文 locale,系統 code page 932,檔案存成 Shift_JIS。回報者在 issue 裡寫了一句:

「Without version control there is no way back.」

沒有版控,就回不去了。原本的位元組沒了,菱形方框也不會告訴你它原本是哪個字。

工具這邊卻回報一切正常。另一個 issue #7134 底下有人在 2.1.295 上重跑同一個 Edit,一樣是 107 → 182 位元組、38 個 U+FFFD,工具回的是「updated successfully」。

叫它小心一點,沒有用

直覺的第一招是在指令裡講清楚:只改這個字,別的不准動。issue 的重現指令本來就是這樣寫的,還把 Bash 關掉,逼它只能用 Edit:

1
claude -p --permission-mode acceptEdits --disallowedTools Bash "Use the Edit tool on the file sjis.py to replace the word APPLE with ORANGE. Change nothing else in the file. Do not use Bash. Reply DONE."

回報者跑了四次,三次 Sonnet 5、一次 Opus 5.5,四次都把 Shift_JIS 的 LF 檔改寫成 UTF-8、留下 38 個 U+FFFD。CRLF 版本跑一次,同樣被改寫成 UTF-8、出現 U+FFFD。

更糟的是模型的回報。其中一次 Sonnet 改完說:

「The other lines are unchanged (still Shift-JIS encoded, same as before)」

這句是假的。Opus 那次有察覺檔案壞了,自己改用 PowerShell 把檔案重寫回來,可是 issue 寫得很清楚,Edit 那一下已經把檔案毀了,PowerShell 是事後補救。

所以「叫它小心」這條路可以直接跳過。模型再謹慎也沒用,Edit 這個工具本身就沒有「只改一行」這種動作。

一台只認得一種字母的影印機

想像一台影印機,它只認得一種字母。你拿一頁文件給它,說「把第三行的 APPLE 改成 ORANGE」。它的做法不是拿立可白塗掉那個字再補上去,是把整頁重新打一遍。打到它認不得的字,它就印一個黑色方塊,然後把新的一頁交給你,說:改好了。

官方 tools-reference 文件講的就是這件事。Edit 和 NotebookEdit 會「save the whole file back as UTF-8」,整個檔案以 UTF-8 存回去,讀不懂的每個位元組都會被換成替代字元 U+FFFD。你要改的只有一行,存回去的卻是整份檔案,所以沒被改到的行一樣遭殃。

那台影印機認得的字母是 UTF-8。Shift_JIS、GBK、Windows-1252 這些舊式編碼存下來的非 ASCII 字,在 UTF-8 眼裡就是一串不合法的位元組。issue #96263 的測試表裡,UTF-8、UTF-8 加 BOM、UTF-16 LE 加 BOM 都沒事,只有沒有 BOM 的 Shift_JIS 出事。回報者的結論是:有 BOM 的檔案會被辨認出來,沒 BOM 的舊 code page 檔案會被當成 UTF-8 讀、再以 UTF-8 寫回。

台灣讀者手上比較常見的是 Big5(cp950)。官方文件和這幾個 issue 都沒有點名 Big5,所以下面這段只能算推論。我用 Python 存了一個兩行的 cp950 檔,裡面有中文註解,然後問它是不是合法 UTF-8:

1
2
# 39 bytes 的 cp950 檔,兩行中文註解與字串
not utf-8: 'utf-8' codec can't decode byte 0xb3 in position 2: invalid start byte

第三個位元組就卡住了。這跟 Claude Code 無關,只說明一般的 Big5 中文檔在位元組層級不是合法 UTF-8,跟 Shift_JIS 同一類。另外有一個同樣是純 Python 的模擬,照「當成 UTF-8 解、遇錯換成替代字元、改一個字、再存回 UTF-8」走一遍:一段 55 位元組的三行繁中樣本,Big5 和 cp950 都變成 87 位元組、16 個 U+FFFD。那是模擬,不是 Edit 的實際實作,數字只能拿來感受量級。

第二條死路:改用 Write 整份重寫

既然 Edit 會出事,換 Write 直接給完整內容總行吧?

issue #101158 在 2.1.296 上測的就是這件事(2026 年 10 月 11 日 UTC 開的)。Windows-1252 和 Shift_JIS 的 CRLF 檔,用 Write 寫完會被重新編碼成 UTF-8,CRLF 也一起變成 LF。Windows-1252 那個檔從 40 位元組變 42,Shift_JIS 從 29 變 34。UTF-16LE 加 BOM 的檔,編碼沒變,BOM 卻被拿掉了。回報者特別點出:每一種情況,Write 回的都是同一句「has been updated successfully」,模型和使用者都不會知道檔案格式變了。

Write 這條路在 2.1.299 有沒有變,我說不準。2.1.299 的 changelog 寫 Write 把 Windows-1252、Shift-JIS、GBK、UTF-16BE 檔「silently converting … to UTF-8」的問題已修,改成「now refused」;可是我 10 月 14 日下載的 tools-reference 文件仍寫著 Write 會覆寫這種檔、並存成 UTF-8,只有新內容含 U+FFFD 時才擋。官方文件與 changelog 說法不一致,我沒有驗證哪個是現況。

從 2025 年 9 月到這週

這個問題不是最近才冒出來的。照時間排一下:

Edit 遇到非 UTF-8 檔的處理演變(時間皆為 UTC)

2025-09-04

issue #7134 開立,回報版本 1.0.103。葡萄牙文 Delphi 專案的 Windows-1252 檔,configuração 被寫成 configuração。到 2026 年 10 月 14 日仍是 open。

2026-09-23

issue #96263 開立,版本 2.1.280。一個字的 Edit 讓整份 Shift_JIS 檔變成 UTF-8 加 38 個 U+FFFD。

2026-10-09

2.1.296 上 npm。changelog 寫 Edit 與 NotebookEdit 遇到不是合法 UTF-8 的檔案,不再替換每個非 ASCII 字元,改成拒絕。

2026-10-10

#7134 有人留言:2.1.295 上同一個 Edit 仍是 107 → 182 位元組、38 個 U+FFFD;升到 2.1.296 後 Edit 被拒絕,檔案維持 107 位元組、仍是合法 CP932。

2026-10-11

issue #101158 開立,在 2.1.296 上記錄 Write 會改掉編碼、BOM 與換行。

2026-10-14

2.1.299 上 npm。changelog 寫修了 Read 讀亂 UTF-16LE 加 BOM 檔的問題,以及 Write 默默轉 UTF-8、丟掉 BOM 的問題(now refused)。

#7134 開著的那一年裡,回報者列出來的 workaround 是另外維護一份 UTF-8 的 repo 複本專門給 Claude Code 用,或寫 pre-commit 腳本在兩種編碼之間來回轉。能用,但等於為了一個工具養兩份程式碼。

2.1.296 真正解掉的是哪一步

2.1.296 沒有讓 Edit 學會讀 Shift_JIS。它做的事情少得多:讀不懂,就不寫。

官方 errors 文件列了 Edit 遇到這種檔案時,模型會在工具結果裡收到的訊息:

1
File is not valid UTF-8. It may use a legacy encoding such as Windows-1252, Shift-JIS or GBK, or be binary. This tool saves the whole file as UTF-8, which would replace every byte it cannot decode with U+FFFD. Nothing was written. Make the change with a shell command that reads and writes the file in its own encoding, or ask the user whether to convert the file to UTF-8 first.

我最在意的是「Nothing was written」這三個字。原本的 bug 有兩層:檔案被毀,還有工具說成功。第二層比較陰險,成功訊息會讓你不去看 diff,等你發現時可能已經 commit、push,甚至部署了。拒絕至少讓錯誤停在當下。錯誤訊息也直接給了模型下一步:用保留原編碼的 shell 指令改,或先問你要不要轉成 UTF-8。

#7134 那則 2.1.296 的留言記錄了後續:同一個 Edit 被拒,檔案維持 107 個位元組。模型接著改用 PowerShell,把那個字改對了,日文保留下來。#96263 的回報者在 issue 裡列的期望行為,其中一條就是這樣:讀不懂就拒絕,並且說出來。對他來說,這個「失敗」才是對的結果。

有兩個邊角值得知道。第一,文件寫明 Edit 的例外是開頭有 little-endian UTF-16 BOM 的檔案,那種它還是會改。第二,本來就該是 UTF-8 的檔案,只要裡面有一個不合法的位元組序列,也會收到同一則訊息,因為它檢查的是整個檔案的位元組。看到這個錯誤,別急著怪編碼,先想想是不是哪裡混進了壞掉的位元組。

還在 2.1.296 以前的版本就不會擋。官方 errors 文件寫得很直接:在那些版本上,Edit 會照改、把讀不懂的位元組存成 U+FFFD,解法是更新 Claude Code。我寫這篇時(10 月 14 日),npm 上最新的是當天 UTC 00:23 上架的 2.1.299。

還沒解的部分

讀這一端還有缺口。#7134 那位留言者在 2.1.296 上指出,Read 仍會把 Shift_JIS/CP932 檔當成 UTF-8 解,模型拿到的內容本身就是亂碼。2.1.299 的 changelog 只寫修了 UTF-16LE 加 BOM 的 Read,Shift_JIS 在 2.1.299 是否還是這樣,我沒有查證。

模型讀到的是亂碼,又被 Edit 擋下來改走 shell,這時檔案能不能保持原編碼,就看它寫出來的那條指令有沒有處理編碼。issue 裡 PowerShell 那次是改對了,可是那是一次的紀錄,不是保證。

Big5 會不會一樣被擋,官方沒寫。依文件「檢查整個檔案位元組」的描述,加上上面那個 cp950 檔在第三個位元組就不合法,我推論一般的 Big5 中文檔會被拒,但沒有實際讓 Claude Code 跑過。

手上有 Big5 舊專案的話

我會先 commit,再轉成 UTF-8,之後照常用 Edit。官方文件給的兩條路,一條是讓 Claude 用 shell 保留原編碼,一條是轉成 UTF-8 再編;前者每次都要賭模型那條指令寫得對,後者轉一次就結束。

macOS 內建的 iconv 認得 CP950。下面的兩條 iconv 我在一個暫存的測試檔上跑過(git 那行沒有):轉成 UTF-8 再轉回 cp950,用 cmp 比對,跟原檔逐位元組相同。不過那只是一個 39 位元組的兩行樣本,不代表你專案裡每個檔都轉得乾淨,Big5 的造字和罕用字尤其要自己確認:

1
2
3
git add legacy.py && git commit -m "轉 UTF-8 前的備份"
iconv -f CP950 -t UTF-8 legacy.py > legacy.utf8.py
iconv -f UTF-8 -t CP950 legacy.utf8.py | cmp - legacy.py && mv legacy.utf8.py legacy.py

例外是檔案下游還有只吃 Big5 的程式,例如舊系統的匯入流程,轉了就讀不懂。那就別轉,讓 Claude 照錯誤訊息的建議用 shell 改。

不管走哪條路,改完都看一次 git diff。只該動一行,diff 卻顯示整份檔案每一行都變了,就是編碼被換掉了。


這篇的事實來源是官方文件、changelog 與 GitHub issue 原文。我沒有讓 Claude Code 的 Edit、Write、Read 對任何 Big5 或 Shift_JIS 檔實際跑過;文中實際執行過的都是跟 Claude Code 無關的純本機操作:用 Python 檢查一個 cp950 樣本檔是不是合法 UTF-8、用 Python 模擬「當成 UTF-8 解再存回」的位元組變化,以及在 cp950 樣本上跑 iconv 來回轉換。issue 的開關狀態以 2026 年 10 月 14 日查詢為準。

原文來源:Claude Code Docs:Tools reference(Non-UTF-8 files)、Claude Code Docs:Errors(File is not valid UTF-8),兩頁皆以 2026 年 10 月 14 日讀取的版本為準;Claude Code CHANGELOG(2.1.296、2.1.299 條目);anthropics/claude-code#96263、#7134、#101158;版本上架時間取自 npm view @anthropic-ai/claude-code time(2026 年 10 月 14 日)。