[BUG] Bundled grep/find/rg shims fail with `-G: error while loading shared libraries` when CLAUDE_CODE_EXECPATH is the dynamic loader (native binary launched via ld.so; NixOS)
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?
Claude Code injects grep/find/rg shell functions (the bundled-search shims) into the Bash-tool shell. Each runs the binary at $CLAUDE_CODE_EXECPATH with a spoofed argv[0], e.g. the grep shim:
( exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git … "$@" )
_cc_bin is $CLAUDE_CODE_EXECPATH, which is derived from the process's own exec path. When the native binary is launched through the dynamic loader — the normal way to run a foreign dynamically-linked binary on NixOS, and common with other FHS/wrapper launchers — execPath is the loader itself, so CLAUDE_CODE_EXECPATH becomes e.g. …/ld-linux-x86-64.so.2.
The shim then effectively runs ld-linux-x86-64.so.2 -G …. ld.so treats -G as a shared object to load, fails, and prints the error below using that same token as the program name. Every bare grep/find/rg in a Bash-tool command then fails with exit 127.
The existing guard [[ -x $_cc_bin ]] does not catch this, because the loader is executable.
What Should Happen?
grep/find/rg should keep working — fall back to the real system tools (command grep, etc.) when $CLAUDE_CODE_EXECPATH does not point at an invocable bundled-search binary.
Error Messages/Logs
$ grep -n foo somefile
-G: error while loading shared libraries: -G: cannot open shared object file: No such file or directory
# exit 127
$ echo "$CLAUDE_CODE_EXECPATH"
/nix/store/…-glibc-2.42-61/lib/ld-linux-x86-64.so.2 # the loader, not the claude binary
Steps to Reproduce
- Run the native
claudebinary in any way that goes through the dynamic loader (on NixOS this is the norm — e.g. the launcher doesexec ld-linux-x86-64.so.2 --library-path … <binary> "$@"). - In a Bash tool call, run any bare
grep,find, orrg(e.g.grep -n foo file). - Observe:
-G: error while loading shared libraries: -G: …, exit 127.
Minimal demonstration of the underlying mechanism on any glibc Linux (simulating CLAUDE_CODE_EXECPATH pointing at the loader, as it does in these setups):
loader=/lib64/ld-linux-x86-64.so.2
exec -a ugrep "$loader" -G --ignore-files --hidden -I foo /dev/null
# → -G: error while loading shared libraries: -G: cannot open shared object file …
Claude Model
Opus (the bug is model-independent — it lives in the Bash tool's shell shim)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Before the bundled grep/find/rg shims were introduced (~v2.1.117 per the changelog); prior versions had no shims, so the commands used the real binaries.
Claude Code Version
2.1.201 (Claude Code)
Platform
Anthropic API
Operating System
Other Linux (NixOS, x86_64, nix-ld enabled)
Terminal/Shell
Other (kitty; the failure is in the Bash tool's spawned non-interactive shell, independent of the outer terminal)
Additional Information
Root cause. The shim assumes $CLAUDE_CODE_EXECPATH is a multicall-capable claude binary, but derives it from process.execPath. Whenever the binary is launched via ld.so, execPath//proc/self/exe is the loader, so the shim execs the loader with search flags. Verified on 2.1.201: CLAUDE_CODE_EXECPATH = …/ld-linux-x86-64.so.2, and the CLAUDE_CODE_EXECPATH shim code is present in the 2.1.201 binary.
Suggested fixes (any one resolves it):
- Harden the fallback —
[[ -x $_cc_bin ]]is insufficient; also skip the shim when$_cc_bin's basename matches a loader (ld-*.so*/ld-linux*), or verify it's actually the claude/multicall binary, and fall back tocommand grep/command find/command rgotherwise. - Don't derive
CLAUDE_CODE_EXECPATHfrom a loader — ifprocess.execPathresolves to a dynamic loader, resolve the real program path (or leave the shims disabled). - A documented opt-out env var (e.g.
CLAUDE_CODE_DISABLE_SEARCH_SHIMS=1; see the previously-proposedCLAUDE_CODE_DISABLE_FIND_SHIMin #53961).
Related: #57362 and #62642 (same shims, other failure modes), #53961 (proposed opt-out), #20012 and #62952 (NixOS / non-FHS).
Workaround for other NixOS users: launch the native binary directly (its interpreter resolves via nix-ld) instead of via ld-linux … <binary>; that keeps execPath/argv[0] on the real binary, so CLAUDE_CODE_EXECPATH is correct and the ugrep/bfs/rg multicall works. Per-command, command grep / command find / command rg bypass the shim.