[BUG] Worktree creation (-w) sets LC_ALL=C in hook subprocess, breaking UTF-8 handling in git hooks
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
In Claude Code 2.1.136, the subprocess that runs git hooks during worktree creation (claude -w) has LC_ALL=C set in its environment, even though the parent shell that launched claude has LC_ALL unset (and LANG=en_US.UTF-8).
LC_ALL overrides LANG, so for any locale-aware program invoked from a hook, the effective locale becomes C (ASCII). This silently breaks UTF-8 file handling in post-checkout hooks. For example, with Ruby:
$ LC_ALL=C ruby -e 'puts Encoding.default_external'
US-ASCII
so File.read('utf8_file_with_japanese.md') returns a US-ASCII-tagged String, and a subsequent regex match against multibyte content raises:
ArgumentError: invalid byte sequence in US-ASCII
Any post-checkout hook that processes UTF-8 files (e.g., reading YAML frontmatter from .md files) silently fails as a result.
This is a regression: the same hooks worked correctly on 2.1.133 and broke on 2.1.136.
What Should Happen?
LC_ALL should not be force-set to C in the env that user-defined git hooks see. It should be inherited from the parent shell (i.e., remain unset in this user's case), so locale-aware programs in hooks behave the same as in a normal terminal session — as was the case on 2.1.133.
Error Messages/Logs
ArgumentError: invalid byte sequence in US-ASCII
Hook env capture (/tmp/hook-env.log) shows:
LANG=en_US.UTF-8
LC_ALL=C ← injected by harness; unset in parent shell
AI_AGENT=claude-code_2-1-136_harness
Steps to Reproduce
- Install a tiny
post-checkouthook in the source repo (the one you'll create the worktree from):
``sh`
#!/bin/sh
env | sort > /tmp/hook-env.log
chmod +x .git/hooks/post-checkout`).
Make it executable (
- From a shell where
LC_ALLis unset andLANG=en_US.UTF-8, run:
``sh``
claude -w
- After the worktree is created, inspect
/tmp/hook-env.log. You will seeLC_ALL=Cset, despite it being unset in the parent shell. - To confirm the user-visible breakage, replace the hook body with a Ruby script that reads a UTF-8 file containing multibyte characters and runs a regex match — it raises
ArgumentError: invalid byte sequence in US-ASCII. - Downgrading to 2.1.133 makes the same hook see the parent's locale (no
LC_ALL=Cinjection), and the Ruby script succeeds.
Hypothesis
LC_ALL=C is a common pattern when invoking git programmatically (it stabilises git's stdout/stderr in English so it's parseable). If the harness is doing something like:
spawn('git', ['worktree', 'add', ...], { env: { ...process.env, LC_ALL: 'C' } })
then the hooks git runs inherit that env. Scoping LC_ALL=C to the output processing of the direct git invocation only (e.g., piping git's stdout through a wrapper that sets LC_ALL=C for its own decoding logic, while letting git itself — and its hooks — run under the user's locale) would restore prior behaviour.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.133
Claude Code Version
2.1.136
Platform
Anthropic API
Operating System
macOS (Darwin arm64)
Terminal/Shell
Other: Ghostty (zsh)
Additional Information
Workarounds (for users hitting this):
- Set
Encoding.default_external = Encoding::UTF_8explicitly at the top of any Ruby hook script. - Or
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8inside the hook before invoking interpreters. - Pin Claude Code to 2.1.133 until this is fixed.
Asks:
- Confirmation of whether this
LC_ALL=Cinjection is intentional. - If intentional, a changelog note / docs page advising hook authors to be locale-defensive would help.
- If unintentional, scoping the override to git's own output handling (not its child processes) would restore prior behaviour.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗