一個字的 Edit,整份日文變成方框:Claude Code 2.1.296 起 Edit 改成拒絕非 UTF-8 檔
要求只有一句:把檔案裡的 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 | # 39 bytes 的 cp950 檔,兩行中文註解與字串 |
第三個位元組就卡住了。這跟 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 | git add legacy.py && git commit -m "轉 UTF-8 前的備份" |
例外是檔案下游還有只吃 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 日)。



















