Skip to content

Commit 399b7a3

Browse files
authored
Redirect bundled Bun's c-ares to a reachable resolv.conf (#26)
Bundled Bun's c-ares resolver (its node:http/axios path: WebFetch's domain preflight, the claude.ai MCP connector, OTEL export) reads the absolute /etc/resolv.conf. Android maps /etc -> /system/etc, which has none, so those lookups hang until timeout while Bun's native fetch (separate DNS) stays fine. Regressed on Claude releases after 2.1.185. Ship a second freestanding LD_PRELOAD shim, resolv-redirect.so, that rewrites opens of /etc/resolv.conf to $PREFIX/etc/resolv.conf. c-ares reads the file via fopen (opaque FILE*), so the shim reaches the real function through dlsym(RTLD_NEXT, ...); dlsym is left undefined and resolved from libc.so.6 with no -ldl, so the object carries no DT_NEEDED (a libdl.so dep would break the glibc ld.so load). The open family guards its mode va_arg on O_CREAT/O_TMPFILE, fails closed when a symbol can't be resolved, and the *64 variants are aliases of their bases. The launcher preloads uname-spoof.so:resolv-redirect.so. Validated end-to-end on the affected 2.1.198 via the built .deb: WebFetch goes from the "unable to verify domain" error to success, while rg/bfs dispatch and startup stay green. Closes #25
1 parent c1aa85f commit 399b7a3

9 files changed

Lines changed: 262 additions & 48 deletions

File tree

AGENTS.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,18 @@ publish the binary; the `.deb` contains no Anthropic bytes.
2727
assignment.
2828
1. The compiled C launcher (`bin/claude`) execs the patched binary —
2929
`execv` **preserves `argv[0]`** (so Claude's embedded `grep`/`find`/`rg`
30-
dispatch works), it **overwrites `LD_PRELOAD` with the uname shim**
31-
(`lib/claude-code-termux/uname-spoof.so`): this both evicts termux-exec's
32-
text-script `libc.so` (which the glibc binary's `ld.so` can't load) and
33-
preloads a freestanding `uname()` interposer that reports a `< 5.11` kernel,
34-
so Bun skips the `epoll_pwait2` path that segfaults at startup on newer
35-
kernels (bun#32489 — see `src/uname-shim.c` and `claude-code#50270`). It also
30+
dispatch works), it **overwrites `LD_PRELOAD` with two freestanding shims**
31+
(`lib/claude-code-termux/uname-spoof.so:…/resolv-redirect.so`): this evicts
32+
termux-exec's text-script `libc.so` (which the glibc binary's `ld.so` can't
33+
load) and preloads both interposers. The **uname** shim reports a `< 5.11`
34+
kernel so Bun skips the `epoll_pwait2` path that segfaults at startup on newer
35+
kernels (bun#32489 — see `src/uname-shim.c` and `claude-code#50270`). The
36+
**resolv** shim redirects opens of the absolute `/etc/resolv.conf` (which
37+
Android maps to the nonexistent `/system/etc/resolv.conf`) to
38+
`$PREFIX/etc/resolv.conf`, so bundled Bun's c-ares (its `node:http`/axios
39+
path — WebFetch's domain preflight, the claude.ai MCP connector, OTEL export)
40+
finds nameservers instead of hanging on a timeout (see `src/resolv-shim.c` and
41+
issue #25). It also
3642
**sets `TMPDIR`/`CLAUDE_CODE_TMPDIR`** to the Termux prefix when unset (Termux
3743
has no writable `/tmp`; see `src/claude-wrapper.c` for the
3844
env-vs-hardcoded-`/tmp` analysis and the MCP-browser-bridge limitation,
@@ -65,14 +71,15 @@ publish the binary; the `.deb` contains no Anthropic bytes.
6571
| `package/payload/libexec/link-native.sh` | Idempotent native-path symlink (`~/.local/bin/claude` → launcher) reconcile, shared by `postinst` and the update command. |
6672
| `package/payload/libexec/patch-execpath.py` | The `CLAUDE_CODE_EXECPATH` patch. |
6773
| `package/payload/etc/claude-code-termux.conf` | `CLAUDE_CODE_VERSION` pin + `CLAUDE_CODE_CACHE_DIR` (both empty by default). |
68-
| `src/claude-wrapper.c` | The C launcher (`-DBINARY=`/`-DTMPDIR_PATH=`/`-DUNAME_SHIM=` baked in at compile). `claude_wrapper_run()` takes its exec function as a parameter — a seam the unit tests fake. |
74+
| `src/claude-wrapper.c` | The C launcher (`-DBINARY=`/`-DTMPDIR_PATH=`/`-DUNAME_SHIM=`/`-DRESOLV_SHIM=` baked in at compile). `claude_wrapper_run()` takes its exec function as a parameter — a seam the unit tests fake. |
6975
| `src/uname-shim.c` | Freestanding `LD_PRELOAD` `uname()` interposer reporting kernel `5.10.0`, so Bun avoids the `epoll_pwait2` startup segfault (bun#32489). Built by `build-wrapper.sh`, shipped to `lib/claude-code-termux/uname-spoof.so`, preloaded by the launcher. |
76+
| `src/resolv-shim.c` | Freestanding `LD_PRELOAD` `fopen`/`open`-family interposer that redirects the absolute `/etc/resolv.conf` (absent on Android) to `$PREFIX/etc/resolv.conf`, so bundled Bun's c-ares resolves DNS on its `node:http` path (WebFetch/claude.ai-MCP/OTEL) instead of hanging (issue #25). References `dlsym` as an undefined symbol resolved from `libc.so.6`**no `-ldl`**, so zero `DT_NEEDED` (a `libdl.so` dep would break the glibc `ld.so` load). Built by `build-wrapper.sh`, shipped to `lib/claude-code-termux/resolv-redirect.so`, preloaded by the launcher alongside the uname shim. |
7077
| `test/wrapper_test.c` | Unit tests for the launcher (greatest; recording exec stub). |
7178
| `test/install_test.sh` | Hermetic unit tests for `install.sh`'s pure helpers (shUnit2; no network/apt). |
7279
| `test/compat.h` | Test-only `setenv`/`unsetenv` shims for Windows libc; force-included into the test build, never shipped. |
7380
| `vendor/greatest.h` | Vendored single-header test framework (greatest 1.5.0, ISC; from silentbicycle/greatest). |
7481
| `vendor/shunit2` | Vendored single-file shell test framework (shUnit2 2.1.9pre, Apache-2.0; pinned to kward/shunit2 master @ `f39734a` — no tagged release since 2.1.8, and master carries the `egrep``grep -E` fix we'd otherwise patch). Drives `scripts/e2e.sh` and `test/install_test.sh`. Carries one local patch (grep `PATCHED FOR TERMUX`; re-apply on bump): `#! /bin/sh` stub scripts → resolve `sh` via PATH (submitted upstream as kward/shunit2#189). |
75-
| `scripts/build-wrapper.sh` | Compile the wrapper + uname shim with Termux's clang. |
82+
| `scripts/build-wrapper.sh` | Compile the wrapper + uname/resolv shims with Termux's clang. |
7683
| `scripts/build-deb.sh` | Stage + `dpkg-deb --build``artifacts/packages/`. |
7784
| `scripts/compile.sh` | Compile the wrapper + assemble the `.deb`, in a Termux env. |
7885
| `scripts/e2e.sh` | Install the prebuilt `.deb` + assert the fixes, in a Termux env. |
@@ -145,11 +152,12 @@ tasks:
145152
`mise run compile` produces the `.deb`, then `mise run test:e2e` installs it via
146153
`install.sh` and asserts the real fixes: `argv[0]=rg`/`bfs` dispatch to the
147154
embedded ripgrep/bfs, startup with `LD_PRELOAD` set (the launcher overwrites it
148-
with the uname shim), a runtime-init boot guard (`claude mcp list` spins up
155+
with the two shims), a runtime-init boot guard (`claude mcp list` spins up
149156
Bun's event loop — catching startup crashes the `--version` fast path can't,
150-
e.g. the Bun 1.4.0 `epoll_pwait2` segfault the uname shim prevents), `TMPDIR`
151-
injection, the `settings.json` merge, the conditional/cached update paths, and
152-
the interpreter-repair `ensure`. The suite is built on the vendored shUnit2 (a
157+
e.g. the Bun 1.4.0 `epoll_pwait2` segfault the uname shim prevents), the resolv
158+
redirect (the shim rewrites a configured path and carries no `DT_NEEDED`, #25),
159+
`TMPDIR` injection, the `settings.json` merge, the conditional/cached update
160+
paths, and the interpreter-repair `ensure`. The suite is built on the vendored shUnit2 (a
153161
single sourced file; the install is its `oneTimeSetUp`, behaviors are
154162
definition-ordered `test_*` functions with the state-mutating ones last). On a
155163
dev host both tasks dispatch to `scripts/docker-run.sh <script>`, which pulls

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,15 @@ re-downloaded. Leave it empty (the default) to disable caching.
7878
(`patch-execpath.py`) so embedded-tool re-execs route back through the
7979
launcher.
8080
1. The compiled launcher execs the patched binary, preserving `argv[0]` so
81-
Claude's `grep`/`find`/`rg` dispatch works. It overwrites `LD_PRELOAD` with a
82-
small `uname` shim: this evicts termux-exec's `libc.so` (which the glibc
83-
binary's loader can't load) and reports a `< 5.11` kernel so the bundled Bun
84-
skips an `epoll_pwait2` path that otherwise segfaults at startup on newer
85-
Android kernels (bun#32489). It also sets `TMPDIR`/`CLAUDE_CODE_TMPDIR` to the
86-
Termux prefix (only when unset) since Termux has no writable `/tmp`.
81+
Claude's `grep`/`find`/`rg` dispatch works. It overwrites `LD_PRELOAD` with
82+
two small shims: this evicts termux-exec's `libc.so` (which the glibc binary's
83+
loader can't load) and preloads a `uname` shim that reports a `< 5.11` kernel
84+
so the bundled Bun skips an `epoll_pwait2` path that otherwise segfaults at
85+
startup on newer Android kernels (bun#32489), plus a `resolv` shim that points
86+
Bun's bundled c-ares resolver at `$PREFIX/etc/resolv.conf` (Android has no
87+
`/etc/resolv.conf`) so WebFetch, the claude.ai MCP connector, and OTEL export
88+
resolve DNS instead of hanging. It also sets `TMPDIR`/`CLAUDE_CODE_TMPDIR` to
89+
the Termux prefix (only when unset) since Termux has no writable `/tmp`.
8790
1. The postinstall also symlinks the launcher onto Anthropic's native-install
8891
path (`~/.local/bin/claude`) so Claude's health check passes when it detects
8992
`installMethod: native`. The symlink targets the launcher (not the patched

mise-tasks/test/greatest

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/usr/bin/env bash
22
#MISE description="Unit-test the C launcher with greatest natively (fast; no Docker)"
33
# Keep Git Bash / MSYS from rewriting the leading-slash sentinel paths
4-
# (BINARY/TMPDIR_PATH/UNAME_SHIM below) into Windows paths on the way to zig
5-
# (no-op elsewhere).
4+
# (BINARY/TMPDIR_PATH/UNAME_SHIM/RESOLV_SHIM below) into Windows paths on the way
5+
# to zig (no-op elsewhere).
66
#MISE env={ MSYS_NO_PATHCONV = "1" }
77
#
88
# Compiles src/claude-wrapper.c with zig and runs it directly — no Termux, no
@@ -19,6 +19,7 @@ set -euo pipefail
1919
BINARY=/claude-code-termux-test/claude
2020
TMPDIR_PATH=/claude-code-termux-test/tmp
2121
UNAME_SHIM=/claude-code-termux-test/uname-spoof.so
22+
RESOLV_SHIM=/claude-code-termux-test/resolv-redirect.so
2223

2324
out="$MISE_PROJECT_ROOT/artifacts/build"
2425
mkdir -p "$out"
@@ -32,7 +33,7 @@ case "${OSTYPE:-}" in msys* | cygwin* | win*) bin="$bin.exe" ;; esac
3233
# -include compat.h shims setenv/unsetenv on Windows.
3334
zig cc -O2 -Wall -Wextra -Werror \
3435
-DBINARY="\"$BINARY\"" -DTMPDIR_PATH="\"$TMPDIR_PATH\"" \
35-
-DUNAME_SHIM="\"$UNAME_SHIM\"" \
36+
-DUNAME_SHIM="\"$UNAME_SHIM\"" -DRESOLV_SHIM="\"$RESOLV_SHIM\"" \
3637
-DCLAUDE_WRAPPER_NO_MAIN \
3738
-include "$MISE_PROJECT_ROOT/test/compat.h" \
3839
-I "$MISE_PROJECT_ROOT/vendor" \

scripts/build-deb.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ stage="$build/${PKG}_${VERSION}_${ARCH}"
4545
echo "error: $build/uname-spoof.so not found — run build-wrapper.sh first." >&2
4646
exit 1
4747
}
48+
[ -f "$build/resolv-redirect.so" ] || {
49+
echo "error: $build/resolv-redirect.so not found — run build-wrapper.sh first." >&2
50+
exit 1
51+
}
4852

4953
# Reset only the staging tree, preserving the compiled wrapper at $build/claude.
5054
rm -rf "$stage"
@@ -63,8 +67,10 @@ install -d "$stage/$PREFIX_REL/libexec/$PKG"
6367
install -d "$stage/$PREFIX_REL/etc"
6468
install -d "$stage/$PREFIX_REL/share/doc/$PKG"
6569
install -m 0755 "$build/claude" "$stage/$PREFIX_REL/bin/claude"
66-
# The wrapper LD_PRELOADs this; its baked-in UNAME_SHIM path must match.
70+
# The wrapper LD_PRELOADs these; their baked-in UNAME_SHIM/RESOLV_SHIM paths
71+
# must match.
6772
install -m 0644 "$build/uname-spoof.so" "$stage/$PREFIX_REL/lib/$PKG/uname-spoof.so"
73+
install -m 0644 "$build/resolv-redirect.so" "$stage/$PREFIX_REL/lib/$PKG/resolv-redirect.so"
6874
install -m 0755 "$root/package/payload/bin/claude-code-termux-update" "$stage/$PREFIX_REL/bin/claude-code-termux-update"
6975
install -m 0755 "$root/package/payload/libexec/bootstrap.sh" "$stage/$PREFIX_REL/libexec/$PKG/bootstrap.sh"
7076
install -m 0755 "$root/package/payload/libexec/link-native.sh" "$stage/$PREFIX_REL/libexec/$PKG/link-native.sh"

scripts/build-wrapper.sh

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ set -euo pipefail
1111
PREFIX=/data/data/com.termux/files/usr
1212
# The wrapper execs the `current` symlink that bootstrap.sh keeps pointing at
1313
# the patched binary, sets TMPDIR to the Termux prefix tmp dir (Termux has no
14-
# writable /tmp), and LD_PRELOADs the uname shim (see src/uname-shim.c). All
15-
# three are baked in at compile time. UNAME_SHIM must match the install path
16-
# build-deb.sh stages the .so to.
14+
# writable /tmp), and LD_PRELOADs two shims (see src/uname-shim.c and
15+
# src/resolv-shim.c). All are baked in at compile time. UNAME_SHIM/RESOLV_SHIM
16+
# must match the install paths build-deb.sh stages the .so files to.
1717
BINARY="$PREFIX/opt/claude-code-termux/current"
1818
TMPDIR_PATH="$PREFIX/tmp"
1919
UNAME_SHIM="$PREFIX/lib/claude-code-termux/uname-spoof.so"
20+
RESOLV_SHIM="$PREFIX/lib/claude-code-termux/resolv-redirect.so"
21+
# Redirect the absolute /etc/resolv.conf (absent on Android) to the prefix copy.
22+
RESOLV_SRC="/etc/resolv.conf"
23+
RESOLV_DST="$PREFIX/etc/resolv.conf"
2024

2125
root=$(cd "$(dirname "$0")/.." && pwd)
2226
out="$root/artifacts/build"
@@ -31,8 +35,16 @@ mkdir -p "$out"
3135
"$CC" -O2 -Wall -Wextra -Werror -shared -fPIC -nostdlib -ffreestanding \
3236
-fno-stack-protector -o "$out/uname-spoof.so" "$root/src/uname-shim.c"
3337

38+
# The resolv shim is freestanding too; it references dlsym (resolved from
39+
# libc.so.6 — deliberately NO -ldl, so no DT_NEEDED; see src/resolv-shim.c) and
40+
# needs -fno-builtin so clang doesn't rewrite the interposed fopen/open calls.
41+
"$CC" -O2 -Wall -Wextra -Werror -shared -fPIC -nostdlib -ffreestanding \
42+
-fno-stack-protector -fno-builtin \
43+
-DRESOLV_SRC="\"$RESOLV_SRC\"" -DRESOLV_DST="\"$RESOLV_DST\"" \
44+
-o "$out/resolv-redirect.so" "$root/src/resolv-shim.c"
45+
3446
"$CC" -O2 -Wall -Wextra -Werror -DBINARY="\"$BINARY\"" -DTMPDIR_PATH="\"$TMPDIR_PATH\"" \
35-
-DUNAME_SHIM="\"$UNAME_SHIM\"" \
47+
-DUNAME_SHIM="\"$UNAME_SHIM\"" -DRESOLV_SHIM="\"$RESOLV_SHIM\"" \
3648
-o "$out/claude" "$root/src/claude-wrapper.c"
3749

3850
echo "$out/claude"

scripts/e2e.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ oneTimeSetUp() {
9595
probe="$(mktemp -d)/env"
9696
clang -O2 -DBINARY="\"$PREFIX/bin/env\"" -DTMPDIR_PATH="\"/PROBE_TMPDIR\"" \
9797
-DUNAME_SHIM="\"$PREFIX/lib/claude-code-termux/uname-spoof.so\"" \
98+
-DRESOLV_SHIM="\"$PREFIX/lib/claude-code-termux/resolv-redirect.so\"" \
9899
-o "$probe" src/claude-wrapper.c >&2 || fatal "probe compile failed"
99100
}
100101

@@ -106,6 +107,27 @@ test_wrapper_is_elf() {
106107
assertEquals 'wrapper is an ELF binary' \
107108
7f454c46 "$(elf_magic "$PREFIX/bin/claude")"
108109
}
110+
test_resolv_shim_installed() {
111+
assertTrue 'resolv-redirect.so installed' \
112+
"[ -f '$PREFIX/lib/claude-code-termux/resolv-redirect.so' ]"
113+
}
114+
test_resolv_shim_is_elf() {
115+
assertEquals 'resolv-redirect.so is an ELF' \
116+
7f454c46 "$(elf_magic "$PREFIX/lib/claude-code-termux/resolv-redirect.so")"
117+
}
118+
# The crux of #25: the shim must carry no DT_NEEDED (a libdl.so dep would break
119+
# the glibc ld.so load; see src/resolv-shim.c). The startup tests catch this
120+
# end-to-end too, but this pinpoints a regression to the shim.
121+
test_resolv_shim_has_no_dt_needed() {
122+
local needed rc
123+
# No 2>/dev/null: a patchelf failure must surface, not be swallowed into an
124+
# empty (assertNull-passing) result. Assert it ran before trusting the output.
125+
needed=$(LD_PRELOAD='' "$patchelf" --print-needed \
126+
"$PREFIX/lib/claude-code-termux/resolv-redirect.so")
127+
rc=$?
128+
assertEquals 'patchelf --print-needed ran' 0 "$rc"
129+
assertNull 'resolv-redirect.so has no DT_NEEDED (no libdl.so)' "$needed"
130+
}
109131
test_bootstrap_installed() {
110132
assertTrue 'bootstrap.sh installed + executable' \
111133
"[ -x '$PREFIX/libexec/claude-code-termux/bootstrap.sh' ]"
@@ -262,6 +284,43 @@ test_startup_with_ld_preload_set() {
262284
"LD_PRELOAD='$preload_lib' '$PREFIX/bin/claude' --version >/dev/null 2>&1"
263285
}
264286

287+
# resolv redirect: compile a shim variant with temp sentinels (the probe
288+
# pattern) + a tiny opener, then prove fopen(SRC) is rewritten to DST (SRC
289+
# absent, so an unredirected open misses). Exercises src/resolv-shim.c directly,
290+
# independent of a c-ares-affected Claude version. See #25.
291+
test_resolv_redirect_rewrites_configured_path() {
292+
local dir src dst rc
293+
dir=$(mktemp -d)
294+
src="$dir/sys-resolv.conf" # stands in for the absolute /etc/resolv.conf
295+
dst="$dir/prefix-resolv.conf"
296+
printf 'nameserver 203.0.113.1\n' >"$dst" # marker; src intentionally absent
297+
clang -O2 -Wall -Wextra -Werror -shared -fPIC -nostdlib -ffreestanding \
298+
-fno-stack-protector -fno-builtin \
299+
-DRESOLV_SRC="\"$src\"" -DRESOLV_DST="\"$dst\"" \
300+
-o "$dir/resolv-redirect.so" src/resolv-shim.c 2>"$dir/cc.log"
301+
rc=$?
302+
assertEquals "resolv shim test build ($(cat "$dir/cc.log"))" 0 "$rc"
303+
cat >"$dir/opener.c" <<'EOF'
304+
#include <stdio.h>
305+
int main(int argc, char **argv) {
306+
FILE *f = fopen(argv[1], "r");
307+
if (!f) { puts("MISS"); return 1; }
308+
char b[64];
309+
if (!fgets(b, sizeof b, f)) b[0] = '\0';
310+
fputs(b, stdout);
311+
fclose(f);
312+
return 0;
313+
}
314+
EOF
315+
clang -O2 -o "$dir/opener" "$dir/opener.c" 2>/dev/null
316+
assertEquals 'opener test build' 0 $?
317+
assertEquals 'absent source path misses without the shim' \
318+
MISS "$("$dir/opener" "$src" 2>&1)"
319+
assertContains 'shim redirects fopen(SRC) to the configured target' \
320+
"$(LD_PRELOAD="$dir/resolv-redirect.so" "$dir/opener" "$src" 2>&1)" '203.0.113.1'
321+
rm -rf "$dir"
322+
}
323+
265324
# TMPDIR injection: Termux has no writable /tmp, so the wrapper sets TMPDIR +
266325
# CLAUDE_CODE_TMPDIR (only when unset). The probe (BINARY=env) prints the env the
267326
# wrapper hands to the exec'd process.

src/claude-wrapper.c

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
* the glibc Claude binary (see the LD_PRELOAD comment in claude_wrapper_run).
1313
*
1414
* BINARY (the absolute path to the patched Claude Code binary), TMPDIR_PATH (the
15-
* Termux prefix tmp dir), and UNAME_SHIM (the absolute path to the uname-spoof
16-
* .so) are baked in at compile time via -DBINARY="…", -DTMPDIR_PATH="…", and
17-
* -DUNAME_SHIM="…".
15+
* Termux prefix tmp dir), UNAME_SHIM, and RESOLV_SHIM (the absolute paths to the
16+
* two LD_PRELOAD shims) are baked in at compile time via -DBINARY="…",
17+
* -DTMPDIR_PATH="…", -DUNAME_SHIM="…", and -DRESOLV_SHIM="…".
1818
*/
1919
#include <errno.h>
2020
#include <stdio.h>
@@ -35,6 +35,11 @@
3535
"UNAME_SHIM must be defined at compile time (-DUNAME_SHIM=\"/…/uname-spoof.so\")"
3636
#endif
3737

38+
#ifndef RESOLV_SHIM
39+
#error \
40+
"RESOLV_SHIM must be defined at compile time (-DRESOLV_SHIM=\"/…/resolv-redirect.so\")"
41+
#endif
42+
3843
/*
3944
* The launch logic, with the exec call injected as a parameter so unit tests
4045
* can substitute a fake and observe the (path, argv) handoff — and the env it
@@ -67,14 +72,14 @@ int claude_wrapper_run(int argc, char **argv,
6772
second, settings-independent layer that travels with every launch.
6873
overwrite=0 so an explicit user value still wins. */
6974
(void)setenv("DISABLE_AUTOUPDATER", "1", 0);
70-
/* Replace (not clear) LD_PRELOAD with the uname shim. termux-exec's
71-
unversioned libc.so text-script crashes the glibc binary's ld.so, so it
72-
must not survive into the exec — and overwriting the variable both evicts
73-
it and preloads our interposer, which reports a < 5.11 kernel so Bun avoids
74-
the epoll_pwait2 startup segfault (bun#32489; see src/uname-shim.c).
75-
overwrite=1: whatever was inherited (termux-exec, or a stale value) is
76-
intentionally displaced. */
77-
(void)setenv("LD_PRELOAD", UNAME_SHIM, 1);
75+
/* Replace (not clear) LD_PRELOAD with our two shims. termux-exec's
76+
unversioned libc.so text-script would crash the glibc binary's ld.so, so
77+
overwriting both evicts it and preloads the interposers: uname-spoof
78+
(src/uname-shim.c) and resolv-redirect (src/resolv-shim.c), both
79+
freestanding glibc ELFs the ugrep/bfs re-exec tolerates. overwrite=1
80+
intentionally displaces whatever was inherited (termux-exec, or a stale
81+
value). */
82+
(void)setenv("LD_PRELOAD", UNAME_SHIM ":" RESOLV_SHIM, 1);
7883
exec(BINARY, argv);
7984
fprintf(stderr, "claude wrapper: execv %s failed: %s\n", BINARY,
8085
strerror(errno));

0 commit comments

Comments
 (0)