claude.exe (linux-arm64 binary) enters infinite recursive self-relaunch loop when run under proot (proot-distro on Termux/Android)

Status Open
Reported on v2.1.233
Maintainer reply None cached
Activity 0 comments · opened Aug 14, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Title: Native linux-arm64 binary infinite-loops when run via proot
(nested proot-distro invocation) on Android/Termux

Environment:

  • Termux (Android, bionic libc) + proot-distro 5.6.0, Ubuntu 24.04 container
  • claude-code-linux-arm64 binary (@anthropic-ai/claude-code v2.1.232),

force-installed since native android-arm64 build is unavailable

  • Confirmed working: proot-distro login ubuntu -- echo hello (clean)
  • Confirmed working: proot-distro login ubuntu --work-dir <dir> -- echo hello (clean)
  • Failing: proot-distro login ubuntu -- .../claude.exe --version

Observed via ps aux: claude.exe spawns a full duplicate of its own
invocation chain (bash -> proot -> proot-distro login ubuntu --work-dir ...
-- claude.exe --version), recursively, until manually killed.

Hypothesis (not confirmed against source, but consistent with proot's
known /proc/self/exe faking): the binary resolves its own executable path
via /proc/self/exe for a self-relaunch step (possibly related to the
bubblewrap sandbox bootstrap), and under proot's /proc emulation this
resolves to the original host invocation command rather than the binary
itself, causing the full original command to be re-executed instead of
a clean self-exec.

This is not a request to support Android/Termux officially — just
flagging a hard infinite-loop/hang behavior that could also affect other
proot/chroot-style sandboxed environments where /proc/self/exe is faked.

Would appreciate confirmation of whether claude.exe relies on
/proc/self/exe for self-relaunch, and if there's an env var to disable
that path (similar to sandbox-disabling flags already documented).

What Should Happen?

The claude-code binary (claude.exe, linux-arm64 build) should start normally
and print its version/run its command without spawning a duplicate of its
own invocation chain.

Specifically: running
proot-distro login ubuntu -- /path/to/claude.exe --version
should behave the same way proot-distro login ubuntu -- echo hello does —
execute once, print output, exit cleanly — rather than recursively
re-launching the entire original command (bash -> proot -> proot-distro
login ubuntu -- claude.exe ...) in a loop.

If this is caused by /proc/self/exe resolving incorrectly under proot's
/proc emulation, the expected/desired behavior would be either:
(a) the binary falls back to argv[0] or another reliable self-path method
when /proc/self/exe doesn't resolve to itself, or
(b) there's a documented env var to skip the self-relaunch step entirely.

Error Messages/Logs

1) Initial symptom — proot-distro's own nested-proot guard fires falsely 
on first attempts:

Error: attempted to run proot-distro in a proot session. Please check 
your system configuration to ensure that this program does not run 
under any other proot instance. Additionally check the target container 
to ensure it does not invoke proot-distro on a loop basis. With 100% 
confidence this is not a mistake. Do not send bug reports!

Debug trace added to proot_distro/cli.py confirmed this fires because 
TracerPid in /proc/self/status legitimately points to proot's own ptrace 
of its child (not an external nested session):

DEBUG tracer_pid=4770 name=Name:        proot

2) After bypassing that guard (for diagnostic purposes only — not a real 
fix), `ps aux` shows the actual infinite loop — claude.exe's full original 
invocation being re-spawned recursively:

u0_a254   9596  proot --kill-on-exit ... --rootfs=.../ubuntu/rootfs 
  ... /bin/bash -c /.../claude-code/bin/claude.exe --version
u0_a254   9602  proot --kill-on-exit ... (second nested proot, same rootfs)
  ... /bin/bash -c /.../claude-code/bin/claude.exe --version
u0_a254   9616  python3.14 /usr/bin/proot-distro login ubuntu 
  --work-dir /data/data/com.termux/files/home 
  -- /.../claude-code/bin/claude.exe --version

This third line is a full re-invocation of proot-distro itself, generated 
internally — not something we called again manually. This repeats 
indefinitely (observed at least 3 full cycles before manual kill).

Confirmed NOT caused by:
- PATH collision with a same-named wrapper script (removed entirely, 
  loop persisted)
- shared .bashrc alias between Termux and container (removed entirely, 
  loop persisted)
- --work-dir flag specifically (isolated test with --work-dir + echo 
  worked cleanly)

Isolated minimal reproduction that works fine (no loop):
$ proot-distro login ubuntu -- echo hello
hello

Isolated reproduction that loops:
$ proot-distro login ubuntu -- /.../claude-code/bin/claude.exe --version
[recursive proot spawn chain as above]

Steps to Reproduce

Environment setup:

  1. Termux (F-Droid build) on Android (tested on Infinix Smart 10,

XOS/Transsion, bionic libc, aarch64).

  1. Install proot-distro and Node.js:

pkg install proot-distro nodejs-lts -y

  1. Install Ubuntu 24.04 container:

proot-distro install ubuntu:24.04

  1. Install claude-code (npm wrapper only installs successfully on

supported platforms; Android is not in the supported list, so the
native binary must be force-installed separately):
npm install -g --allow-scripts=@anthropic-ai/claude-code @anthropic-ai/claude-code
npm install -g --allow-scripts=@anthropic-ai/claude-code @anthropic-ai/claude-code-linux-arm64 --force

  1. Link the forced binary into the expected wrapper location:

node -e "
const fs=require('fs'),path=require('path');
const base='/data/data/com.termux/files/usr/lib/node_modules/@anthropic-ai';
const src=path.join(base,'claude-code-linux-arm64/claude');
const dest=path.join(base,'claude-code/bin/claude.exe');
fs.mkdirSync(path.dirname(dest),{recursive:true});
fs.copyFileSync(src,dest);
fs.chmodSync(dest,0o755);
"

Reproduction:

  1. Run the binary directly through proot-distro (no custom wrapper

script involved at this point):
proot-distro login ubuntu -- /data/data/com.termux/files/usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe --version

  1. Observe: the command hangs indefinitely (does not print a version

and does not exit).

  1. In a second Termux session, run:

ps aux | grep -E "proot|claude"

  1. Observe: multiple nested "proot ... claude.exe --version" processes,

including a duplicate "proot-distro login ubuntu --work-dir ...
-- claude.exe --version" invocation that was never issued manually —
it is spawned internally, recursively, and the count keeps growing
over time until the processes are killed manually:
proot-distro kill --all
pkill -9 -f claude.exe

Control test (confirms proot-distro itself is not at fault):

  1. proot-distro login ubuntu -- echo hello

→ returns "hello" cleanly, no recursion, confirming the loop is
specific to launching claude.exe and not to proot-distro/login/
--work-dir mechanics in general.

Claude Model

Other

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.233 (unable to run claude --version directly due to the reported bug — version confirmed via npm view @anthropic-ai/claude-code version and package.json inspection instead)

Platform

Other

Operating System

Other

Terminal/Shell

Other

Additional Information

Full reproduction steps and process-tree evidence (ps aux showing the
recursive proot spawn) are in the "Steps to reproduce" and "Error logs"
sections above. Root cause hypothesis: proot's /proc emulation likely
returns an incorrect self-path when claude.exe queries /proc/self/exe
for self-relaunch, causing it to re-exec the full original host command
(including the proot-distro wrapper) instead of itself.

Also note: proot-distro's own nested-session guard (_refuse_nested_proot
in cli.py) has a related false-positive issue on this platform — it
misidentifies proot's own ptrace of its child as an external nested
session (TracerPid check). This may be worth reporting separately to
termux/proot-distro, but is included here for context since it was
encountered during diagnosis.

Diagnostic note: to trace the recursion, I temporarily patched cli.py
to disable that nested-session guard (return instead of crit_error) and
added debug print statements to inspect TracerPid. This was done purely
for diagnosis on my own local copy and has since been reverted —
mentioning it in case it's relevant to interpreting the process traces
above.

Separately: I don't currently have a paid Pro/Max/Team subscription or
Anthropic API account, so I have not been able to test whether the login
flow itself would work past this point — this bug blocks execution
before authentication is ever reached.

View original on GitHub ↗