ugrep grep shim breaks when CLAUDE_CODE_EXECPATH is a dynamic loader (Termux glibc): '-G: cannot open shared object file', exit 127
What happened
On Termux/Android, the glibc build of Claude Code is launched through the glibc
dynamic loader (ld-linux-aarch64.so.1 /path/to/claude), because Bionic cannot
exec the glibc binary directly. In that setup Claude Code sets:
CLAUDE_CODE_EXECPATH=/data/data/com.termux/files/usr/glibc/lib/ld-linux-aarch64.so.1
i.e. the loader, not the Claude binary.
The grep shell function Claude Code injects into Bash-tool shells routes greps
through its bundled ugrep:
( exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@" )
with _cc_bin="${CLAUDE_CODE_EXECPATH:-}". Because _cc_bin is the loader, the
loader treats -G as the program to load and every such grep fails:
-G: error while loading shared libraries: -G: cannot open shared object file: No such file or directory
The guard above it ([[ -x $_cc_bin ]] || _cc_bin=~/.local/bin/claude, then fall
back to command grep if not executable) does not help: the loader is
executable, so the shim proceeds.
Why this is worse than a noisy error
The failure exits 127. That is non-zero, so it takes the same branch as "no
match":
cmd | grep -q pattern && do_thing # silently behaves as "pattern absent"
So it produces false negatives in conditionals, not just visible errors.
Reproducer
# inside a Claude Code Bash tool call, on a loader-launched install
echo "$CLAUDE_CODE_EXECPATH" # -> .../ld-linux-aarch64.so.1
echo hello | grep hello; echo "exit=$?" # -> -G: error..., exit=127
grep hello /etc/hostname # standalone may work if something else
# intercepts `grep` before the function
Direct confirmation that the loader is the failing component:
/data/data/com.termux/files/usr/glibc/lib/ld-linux-aarch64.so.1 -G --hidden pat file
# -> -G: error while loading shared libraries: -G: cannot open shared object file
Workaround
Force the shim's own fallback by making CLAUDE_CODE_EXECPATH non-executable,
via env in settings.json:
{ "env": { "CLAUDE_CODE_EXECPATH": "/nonexistent" } }
Then grep resolves to command grep and works normally (verified: piped
greps, grep -q conditionals and grep-to-grep pipes all exit 0).
Note ~/.bashrc is not a viable place for this fix — Bash-tool shells source a
generated snapshot, and snapshots do not carry env exports.
Suggested fix
Validate that CLAUDE_CODE_EXECPATH is the Claude executable rather than merely-x before using it as the ugrep host — e.g. reject it when it is a dynamic
loader / when exec -a ugrep "$_cc_bin" -G --version does not succeed — and
fall back to command grep otherwise.
Environment
- Claude Code 2.1.221 (glibc build, launched via
ld.so) - Termux on Android, aarch64, kernel 4.14.356
- Shell: bash (glibc), Bash tool
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗