Troubleshooting Claude Code on Android

July 27, 2026 · View on GitHub

This guide covers problems specific to running Claude Code on aarch64/ARM64 Android 8+ with Termux (Path A and Path B), plus a Path C section below that does not use Termux. Path A, B, and C are the three install methods described in the README: Path A (native Termux), Path B (proot-distro Ubuntu), and Path C (the Android Virtualization Framework, or AVF, a real Linux VM built into newer Android). Android 8 / 9 have OAuth caveats (see FAQ). Each entry starts with the error you see, then the fix, then the explanation.

If you haven't installed yet, see install.md first.

Which version do I have? Some entries below are labelled by version, for example "(v2.x install)" or "(v2.9.1+)". Those refer to this repo's install (currently 2.9.x), which is a different number from Claude Code's own version (currently 2.1.x). Note that claude --version reports Claude Code's number (2.1.x), not this repo's, so it does not tell you which setup you have. For a one-command check that does, see Which setup am I on? below.


Table of Contents


Which setup am I on?

This repo's own version (currently 2.9.x) is a different number from Claude Code's own version (currently 2.1.x); the two are unrelated. claude --version reports Claude Code's number (2.1.x) on every setup, so it does not tell you which of this repo's installs you have. To find that out deterministically, check what is on disk:

if [ -d "$PREFIX/lib/node_modules/@anthropic-ai/claude-code" ]; then
  echo "pinned install (Claude Code 2.1.112 via npm; treat as a v2.x setup)"
elif ls "$HOME/.local/share/claude/versions/"*.*.* >/dev/null 2>&1; then
  echo "native Path A install (the v2.9.x auto-updating wrapper)"
else
  echo "no recognized claude install found here"
fi

If it prints pinned install, follow the entries labelled "(v2.x install)" and the pinned upgrade path. If it prints native Path A install, you are on the v2.9.x setup and can skip the "(v2.x install)" entries.


Claude crashes immediately on launch

Applies to Path A (native Termux).

You see: one of these the instant you run claude, often right after it had been working fine:

Bad system call

or

oh no: Bun has crashed. This indicates a bug in Bun, not your code.

or a Segmentation fault message that links to bun.report. Tellingly, claude --version still prints a version, but claude on its own dies straight away.

What happened: Claude Code auto-updated itself to a release that does not run on Android. Claude Code is a single native program, and a recent build of it asks Android for something Android does not allow, so it stops the moment it starts. This is a bug in that release, not in your phone, and not anything you did.

Fix it:

On Android 8, 9, or 10: the native binary cannot run on your device at all (tripping Android's seccomp filter is what caused the crash), so re-running install.sh will not help; it would just reinstall the same native binary. Install the pinned version instead, an older Claude Code that runs on these devices:

curl -fsSL https://raw.githubusercontent.com/ferrumclaudepilgrim/claude-code-android/main/install-pinned.sh -o install-pinned.sh
bash install-pinned.sh

Your login and settings are kept. (Not sure which Android version you are on? Settings > About phone > Android version.)

On Android 11 or newer: re-run this repo's installer. However you first installed, on a phone that already has this setup it refreshes the launcher in place, without the large re-download, updating it to a version that checks each Claude Code update before running it and, on its own, falls back to the last one that worked. Your login and settings are kept.

curl -fsSL https://raw.githubusercontent.com/ferrumclaudepilgrim/claude-code-android/main/install.sh -o install.sh
bash install.sh

Then start Claude Code the normal way. The refreshed launcher checks the installed version and, if it crashes, rolls back to the last one that worked, with no further action from you.

If you are still on the older pinned install (Claude Code 2.1.112 with the auto-updater off), install.sh will point you to migrate.sh instead. Run that: it upgrades you to the current self-healing setup.

If you then see no working claude binary found: your phone has no good version saved to fall back to. Install the pinned version with the install-pinned.sh command shown above, an older Claude Code that does not have this problem.

The pinned version is a safety net, not a one-way street: once a working Claude Code release is out, re-run install.sh to move back to the current, self-healing setup.

Running Claude Code inside proot-distro Ubuntu (Path B) is another way around it; see the install guide.

Why it happens (background, optional)

Two different failures produce these messages. Bad system call is Android's seccomp filter (a kernel feature that blocks a program from making system calls it is not allowed to) doing its job: the native binary issues a low-level system call the filter does not allow, and Android stops it. Which call trips it is version specific (an Android 10 build has died on statx, a newer native build on pidfd_open). Segmentation fault or oh no: Bun has crashed on newer phones is a different problem: it is a null pointer crash inside Termux's glibc-runner shim for epoll_pwait2, which the Bun 1.4 runtime calls at startup, not a blocked system call. In both cases claude --version survives because it exits before it reaches the crash; a real launch does not. The launcher in v2.9.2 and later tests each version this way and rolls back when one fails. The Bun side is fixed upstream in oven-sh/bun#32490 (the runtime issues a raw system call to skip the shim).


Update says "checksum mismatch" or stays on an older version

Applies to Path A (native Termux).

You see: running claude prints an update line to stderr and keeps running the version you already had, for example:

sha256sum: .../versions/2.1.203.tmp: No such file or directory
[claude] update: checksum mismatch on 2.1.203, using cached

or [claude] update: download incomplete, using cached. Claude still starts on the cached version; it just does not move up to the new release.

What happened: a transient hiccup in the once-a-day update check, most often two claude launches within the same few-minute download window on a launcher from before 2.9.4. The published release itself is fine (its checksum is valid); the download on this device did not finish cleanly that time. The launcher then records the daily check as done, so it does not retry until the next day.

Fix it: force one clean update, in a single terminal with no other claude running:

claude --update-now

That downloads the current release, verifies it, and swaps it in. If it still reports a problem, refresh the launcher itself, which also delivers the 2.9.4 launcher that takes a lock so two launches can no longer collide:

curl -fsSL https://raw.githubusercontent.com/ferrumclaudepilgrim/claude-code-android/main/install.sh -o install.sh
bash install.sh

Your login and settings are kept. A one-off checksum mismatch is not a corrupted file you need to clean up by hand: the launcher already discards the partial download.


EACCES error printed at startup

Applies to Path A (native Termux).

You see: an error line on startup mentioning Cannot read directory, while Claude Code itself works normally:

$ claude --version
2.1.207 (Claude Code)
error: Cannot read directory "/": EACCES

It shows up on claude, claude --version, and claude mcp list, but not on claude --help. Claude runs fine, sessions work, and the exit code is 0.

What happened: the launcher loads a small DNS helper at startup. While resolving where that file lives, the runtime walks up the folders above it, one at a time, until it reaches the top of the filesystem. Android does not let an app read that top-level folder, so the runtime reports the refusal and carries on. It is noise, not a failure: nothing is broken and nothing is skipped.

Fix it: this is fixed in 2.9.4, which loads the helper by a path relative to where you are, so the walk never happens. Refresh the launcher:

curl -fsSL https://raw.githubusercontent.com/ferrumclaudepilgrim/claude-code-android/main/install.sh -o install.sh
bash install.sh

Your login and settings are kept. If the line still appears afterwards, it is still harmless, and the DNS fix keeps working either way; the launcher falls back to the absolute path when it cannot work out a relative one.


Claude hangs on "Checking connectivity" / ETIMEOUT / 'API error' on every message

You see: Claude starts, then hangs on Checking connectivity... and fails with:

Unable to connect to Anthropic services
Failed to connect to api.anthropic.com: ETIMEOUT

even though curl https://api.anthropic.com/ connects fine over both IPv4 and IPv6. It is often intermittent (fine one launch, hung the next) and is unrelated to your Claude Code version or to running as root. (On an already-logged-in session it may instead surface as an API error when you send a message.)

Fix: Update to the latest install.sh (or just re-run if using the curl + bash command). The launcher now points Claude Code's DNS resolver at a working nameserver automatically: it writes a tiny setdns.js next to the binary and loads it with BUN_OPTIONS=--preload on every launch (no binary patching, no extra permissions, no daemon). To confirm it's in place:

cat ~/.local/share/claude/setdns.js   # -> require("dns").setServers(["8.8.8.8", ...])

If you'd rather fix it by hand without re-running the installer, write the same preload file the installer uses and load it on each launch. Writing it to the installer's path means the verify command above works for the manual fix too, and the ~/.bashrc line makes it persist across shells (the installer's wrapper persists it for you; the manual path has no wrapper, so it needs this line):

mkdir -p ~/.local/share/claude
echo 'try { require("dns").setServers(["8.8.8.8","8.8.4.4"]); } catch(e){}' > ~/.local/share/claude/setdns.js
echo 'export BUN_OPTIONS="--preload $HOME/.local/share/claude/setdns.js"' >> ~/.bashrc
export BUN_OPTIONS="--preload $HOME/.local/share/claude/setdns.js"

Heads up on your DNS: the preload points c-ares at Google's public resolvers (8.8.8.8 / 8.8.4.4), so Claude Code's own lookups go there instead of a private, VPN, or Pi-hole resolver you may run. The rest of your phone keeps using its normal DNS; this only affects claude. To send claude's lookups to your own nameserver instead, edit the setServers([...]) list in ~/.local/share/claude/setdns.js.

Why it happens (background, optional)

Claude Code is built with Bun, whose DNS resolver (c-ares) reads /etc/resolv.conf to find a nameserver. On Android /etc is a read-only link to /system/etc with no resolv.conf, so c-ares falls back to its built-in default 127.0.0.1:53, where nothing is listening. The glibc resolver Termux ships does work (it reads $PREFIX/glibc/etc/resolv.conf, where $PREFIX is Termux's own install directory), but Bun uses both, and under the burst of name lookups at startup the dead-loopback queries time out and starve the working path. curl is unaffected because it uses Android's own (bionic/netd) resolver. The preload calls dns.setServers() to hand c-ares a live nameserver before the first lookup, so it never hits the dead loopback.


Unsupported architecture: armhf

You see:

Unsupported architecture: armhf. Only amd64, arm64 are supported.

Fix: None. Claude Code requires a 64-bit (arm64/aarch64) operating system. Your device is running a 32-bit OS.

Check your architecture:

uname -m

If the output is armv7l or armv8l, your device cannot run Claude Code. This is a hard requirement with no workaround.

Why this happens: Some budget Android phones (Samsung Galaxy A13 5G, A02S, M13 5G, and others) ship with a 32-bit Android OS on 64-bit hardware. The phone's marketing materials may say "64-bit processor" but the OS runs in 32-bit mode. Claude Code checks process.arch at startup and rejects anything other than arm64 or x64.

Affected devices include: Samsung Galaxy A13, A02S, M13 5G, A10, A6, and similar budget models from 2018-2023. Any phone where uname -m returns armv7l is affected regardless of the CPU's theoretical capability.


Claude Code won't start, no error (v2.x install)

You see: The command returns immediately to your shell prompt. No output, no error, no crash log.

$ claude
$

Affected: v2.x users on the pinned 2.1.112 npm install. The v2.9.0 install (current install.sh) does NOT exhibit this symptom: it runs the linux-arm64 native binary directly under glibc-runner (a Termux package providing a glibc-compatible dynamic linker for Linux binaries) rather than the npm-installed JS bundle.

Cause (v2.x): The in-process auto-updater replaced the pinned 2.1.112 install with a 2.1.113+ build that has no android-arm64 binary. The replaced binary loads but exits before producing output. The chmod -R a-w lock applied by the v2.x install.sh should prevent this; if the install dir is no longer read-only, the lock was undone.

Recovery (v2.x): Re-run a fresh install.sh. This now installs the v2.9.0 architecture, which sidesteps the issue entirely. Or, if you must stay on v2.x, manually reinstall 2.1.112:

chmod -R u+w $PREFIX/lib/node_modules/@anthropic-ai/claude-code/ 2>/dev/null
DISABLE_AUTOUPDATER=1 npm install -g @anthropic-ai/claude-code@2.1.112
chmod -R a-w $PREFIX/lib/node_modules/@anthropic-ai/claude-code/

If bare claude exits silently after that, confirm ls -la $PREFIX/lib/node_modules/@anthropic-ai/claude-code shows dr-x------.


Claude Code exits: "native binary not installed" (historical / v2.x context)

You see:

$ claude
Error: claude native binary not installed.

Either postinstall did not run (--ignore-scripts, some pnpm configs)
or the platform-native optional dependency was not downloaded
(--omit=optional).

Affected: Users on a v2.x install whose pinned 2.1.112 was clobbered by the in-process auto-updater pulling a 2.1.113+ build (no android-arm64 binary). The v2.9.0 install does not exhibit this: it runs the linux-arm64 native binary directly via glibc-runner.

Cause: Upstream regression introduced in @anthropic-ai/claude-code 2.1.113. Versions 2.1.113 and later switched from a bundled cli.js JavaScript entry point to a platform-native binary wrapped by an optional-dependency dispatcher; android-arm64 is not in the dispatcher's PLATFORMS map. Tracked upstream at anthropics/claude-code#50270.

Recovery (recommended): upgrade to v2.9.0:

# 1) Remove the v2.x install
chmod -R u+w $PREFIX/lib/node_modules/@anthropic-ai/claude-code/ 2>/dev/null || true
rm -rf $PREFIX/lib/node_modules/@anthropic-ai/claude-code/ $PREFIX/bin/claude
rm -f $HOME/.claude/settings.json   # optional; will be rewritten

# 2) Run the v2.9.0 install.sh
curl -fsSL https://raw.githubusercontent.com/ferrumclaudepilgrim/claude-code-android/main/install.sh -o install.sh
bash install.sh

Recovery (stay on v2.x):

chmod -R u+w $PREFIX/lib/node_modules/@anthropic-ai/claude-code/ 2>/dev/null
DISABLE_AUTOUPDATER=1 npm install -g @anthropic-ai/claude-code@2.1.112
chmod -R a-w $PREFIX/lib/node_modules/@anthropic-ai/claude-code/
echo 'export DISABLE_AUTOUPDATER=1' >> ~/.bashrc

The chmod -R a-w was load-bearing under v2.x: the in-process auto-updater re-fetched latest on a timer and silently overwrote the install dir. v2.9.0 sidesteps the entire mechanism (autoUpdates: false in settings.json + a wrapper at $PREFIX/bin/claude, where $PREFIX is Termux's package prefix directory, outside the npm tree).

Path B (proot-distro Ubuntu) was never affected by this regression. Inside the Ubuntu guest, process.platform === 'linux' matches the upstream linux-arm64 native binary directly.


Claude can't find a tool (jq / git / python / ...)

You see: Claude tries to run jq, git, python, gh, openssh, tree, etc. and the command fails with command not found. Tool calls fail repeatedly with the same kind of error.

Cause: This is a vanilla Claude Code in an environment it is not used to. install.sh installs the claude binary and the glibc-runner/patchelf-glibc support it needs (glibc-runner provides a glibc-compatible dynamic linker for Linux binaries; patchelf-glibc is a Termux-packaged patchelf utility for modifying ELF binary metadata so they resolve against it). It does not install nodejs or most developer tools. Beyond what Termux core ships (unzip, tar, gzip, less, nano), most tools Claude reaches for are not present.

Fix: Install the Recommended Common Packages in install.md (one pkg install line). Also consider injecting them into your CLAUDE.md or an environment hook so Claude knows what's available. Without that, expect recurring tool failures and barriers.


OAuth / authentication fails on first launch

You see: The OAuth (browser-based login) flow fails, hangs, or the browser never opens. You may see:

Error: Failed to open browser

Or the auth URL prints to the terminal but nothing happens when you visit it, or the redirect back to localhost fails with a connection refused error.

Fix:

  1. Install termux-open-url to enable browser integration:

    pkg install termux-tools -y
    

    Then retry claude; it should open your system browser for OAuth.

  2. If the browser opens but the redirect fails, copy the auth URL manually from the terminal into your browser.

  3. If all else fails, try authenticating with a direct API key:

    export ANTHROPIC_API_KEY="your-key-here"
    

Cause: Termux has no system browser integration by default. The login redirect URL may not reach Termux because localhost inside Termux and localhost from the Android browser are not always the same network context.

Android-version note: Auto-open behavior varies by Android version. On Android 10 and newer the system browser generally opens on its own when claude triggers OAuth. On Android 8 and 9 it may not: if the browser does not open, copy the URL from the terminal and paste it into your phone's browser manually. See FAQ: Claude prints a URL but my browser doesn't open.


proot-distro issues

You see: Inside a proot-distro guest (Ubuntu, Debian, etc.), Claude Code produces no output or hangs.

$ proot-distro login ubuntu
root@localhost:~# claude

Current status: A TCGETS2 ioctl bug that previously broke stdout in guest distros with glibc 2.41+ was fixed in proot 5.1.107-66 (October 2025). On the devices I have tested (Android 13 and Android 17), proot-distro works with current proot versions (5.1.107-66+). If you are seeing this issue on a different Android version, update proot first:

pkg upgrade proot proot-distro -y

If it still hangs after updating proot:

  1. Check your proot version. Must be 5.1.107-66 or later:

    dpkg -s proot | grep Version
    
  2. Test with a simple command instead of interactive login:

    proot-distro login ubuntu -- sh -c 'echo hello'
    
  3. If the simple command works but interactive login hangs, the issue may be terminal initialization. Try:

    proot-distro login ubuntu -- bash --norc --noprofile
    

The warning can't sanitize binding "/proc/self/fd/1" appears during proot-distro login and is harmless. stdout works correctly despite this message.

Note: proot-distro is a valid alternative to the native Termux approach. See install.md, Path B for the full setup guide. However, for Claude Code alone, the native Termux approach (Path A) is lighter and faster.


Node.js v24 hangs

Affected: the pinned 2.1.112 JS install, which runs on system Node. The Path A native binary ships its own runtime and no Node, so node -v and this hang do not apply to it.

You see: Claude Code hangs on startup with Node.js v24. The process appears to start but never becomes interactive.

$ node -v
v24.x.x
$ claude

There is no error message. The process appears to start but never becomes interactive.

Fix: The v24 hang is specific to v24. Upgrade Node to v25 or later:

pkg upgrade nodejs -y
node -v  # should show v25.x.x or higher

If pkg upgrade doesn't move you to v25, check that your Termux package repositories are current. The F-Droid version of Termux ships v25+ in its default repo. As a fallback, use Path B (proot-distro Ubuntu), where this constraint does not apply.

Cause: The hang is specific to v24, not Termux generally. Node.js v24+ inside proot-distro Ubuntu does not exhibit this behavior in testing. v25 resolves it.


Process killed randomly

You see: Claude Code or its subprocesses die mid-session. No error, no crash; the process just disappears. Your terminal may show:

[Process completed (signal 9) - press Enter]

Or the Claude Code session simply vanishes and you're back at your shell prompt.

Fix:

  1. Minimize background apps while using Claude Code.

  2. Limit concurrent subagents and child processes.

  3. Enable the developer option to disable the restriction:

    Settings -> Developer Options -> Disable child process restrictions (toggle on)

    If you don't see Developer Options, go to Settings -> About phone and tap Build number seven times.

Cause: Android's phantom process killer. Android limits background processes to approximately 32 across all apps. When Termux spawns multiple Node.js processes (Claude Code, subagents, language servers), the OS silently kills excess processes.

Session persistence with tmux: Install tmux (pkg install tmux) and run Claude Code inside a tmux session (tmux new -s claude). If Android kills the Termux app, your session survives. Reopen Termux and run tmux attach -t claude to resume.


EMFILE errors

You see:

Error: EMFILE: too many open files, open '/data/data/com.termux/files/home/...'

or

Error: EMFILE, too many open files

Fix:

  • Check your limit: ulimit -n. This varies by device and Android version; newer devices report tens of thousands, older ones can be lower.
  • Avoid spawning unnecessary background processes.
  • Close unused terminal sessions.
  • If running multiple tools simultaneously, reduce parallelism.
  • Restart Claude Code to release leaked file descriptors.

Cause: EMFILE means the process ran out of file descriptors. The limit varies by device and Android version. If you hit this, reduce concurrent operations or check if leaked FDs are the real issue (ls /proc/$$/fd | wc -l).


npm install fails silently (your own packages, not Claude Code)

You see: You run your own npm install -g <some-package> and npm appears to finish without complaint, but the package is not actually installed. This is for your own npm work; install.sh in this repo handles the Claude Code install correctly without any extra setup.

Fix: Run npm install with bash install.sh's shell environment, or call npm from a shell where the install script's environment has already been applied. If you are installing packages outside Termux's default location, you may need to set npm config set prefix to a writable path you own.

Cause: npm needs a writable working directory at install time. Termux's default shell environment provides one when set up by this repo's install.sh. If you launch npm from a stripped environment (cron, a shell with env -i, a non-interactive session that did not source your shell profile), npm may fail to stage files and exit quietly. This is generic Termux/npm behaviour; it does not affect the Claude Code install path documented in this repo.


Grep/Glob/slash commands fail with ENOENT (v2.x install)

You see:

spawn /data/data/com.termux/files/usr/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-android/rg ENOENT

Search tools (Grep, Glob) and slash commands that depend on them crash immediately. Claude Code may fall back to slower methods or simply fail the operation.

Affected: v2.x installs only. On v2.9.0, the linux-arm64 native binary ships vendor/ripgrep/arm64-linux/rg and reports process.platform === 'linux', so it finds its bundled rg on the first try. No env var or symlink needed.

Recovery (v2.x / pinned 2.1.112): Install system ripgrep and symlink it onto the exact vendored path Claude Code looks for. This is what scripts/fix-ripgrep.sh does:

pkg install ripgrep -y
VEN="$PREFIX/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/arm64-android"
mkdir -p "$VEN"
ln -sf "$(command -v rg)" "$VEN/rg"

Re-run after a Claude Code update; the symlink does not survive updates.

Cause: The v2.x / pinned 2.1.112 install uses the npm-distributed JS bundle whose vendored ripgrep set has no arm64-android build, so the search tool spawns a path that does not exist. On 2.1.112 the CLAUDE_CODE_USE_NATIVE_FILE_SEARCH=1 env var does not redirect the search, so the symlink onto the vendored path is the fix.

Custom agents (v2.x)

If custom agents defined in .claude/agents/ fail to load on v2.x, it is the same root cause: Claude Code's file search cannot find the agent definition files on arm64-android. The ripgrep symlink above resolves agent loading as well. v2.9.0 does not exhibit this issue.


#!/usr/bin/env scripts fail with "bad interpreter" (v2.9.1+)

You see:

/usr/bin/env: bad interpreter: No such file or directory

A script that claude's Bash tool runs directly and whose first line is #!/usr/bin/env bash (or python3, node, and so on) exits 126. This shows up with some project scripts and node CLI shims.

Affected: v2.9.1 and later. The install no longer sets env.LD_PRELOAD in ~/.claude/settings.json, because that preload broke claude's own bundled grep, rg, and ugrep. Android has no /usr/bin/env; Termux's termux-exec preload is what rewrites /usr/bin/env to Termux's own env, and claude's subprocesses no longer carry that preload. This is a deliberate trade-off in favour of search working.

Work around it: run the interpreter explicitly instead of executing the file:

bash ./script.sh      # instead of ./script.sh
python ./script.py    # instead of ./script.py
node ./tool.js        # instead of ./tool.js

Tools called by name (grep, git, python, node) and #!/bin/sh scripts are unaffected, and a normal Termux shell outside claude is unaffected.


Voice mode not functional

You see:

Voice mode requires SoX for audio recording. Install SoX manually:

The /voice command refuses to start.

Fix:

pkg install sox termux-api -y

Then grant microphone permission to Termux when Android prompts you (or manually via Settings -> Apps -> Termux -> Permissions -> Microphone).

Note: Voice mode functionality may still be limited on Android even after installing SoX and granting permissions. Audio routing on Android does not always cooperate with command-line tools.

Cause: SoX (a command-line audio processing tool) is available in Termux (pkg install sox) but voice mode also needs microphone access, which requires the Termux:API addon app and Android microphone permissions granted to Termux.

Android 16 microphone input: Microphone capture for voice mode is currently broken on Android 16 (the module-sles-source audio module does not work there), so voice input may not record. Voice output via termux-tts-speak is unaffected and works today. A fix is in progress upstream in termux-packages.


Hooks on Termux native

Status: Hooks fire correctly on current Claude Code releases on Termux native (process.platform === "android"). SessionStart and PreToolUse hooks with command-type entries and matchers like Write|Edit|Read|Glob|Grep|Bash execute as expected.

You can verify the platform identifier (informational, not a blocker):

$ node -e "console.log(process.platform)"
android

This node -e check needs a separate Node install (the pinned 2.1.112 JS path or Termux's own nodejs). The Path A native binary reports process.platform === "linux" internally and ships no Node, so the check does not apply to it. Hooks fire either way.

If your hooks are not firing on Termux:

  1. Confirm the hook command path resolves on Termux ($HOME and $PREFIX expand inside the JSON config; absolute paths also work)
  2. Set timeout explicitly in the hook entry (some hooks fail silently without one)
  3. Verify hook exit codes (PreToolUse: 2 = block, 0 = allow; non-zero non-2 = non-blocking error)
  4. For PreToolUse/PostToolUse, confirm the matcher pattern (regex) actually matches the tool name
  5. Check ~/.claude/logs/ if present

Termux from Google Play has issues

You see: If you installed Termux from a source that turned out to be outdated or limited, packages may fail to install, repositories may be missing, or the app may behave unexpectedly:

E: Unable to locate package nodejs

You may also see apt report that a repository "does not have a Release file," which means the build is pointing at a package source it can no longer reach.

Fix: Uninstall the current Termux and install from one of these sources:

After installing, run:

pkg update && pkg upgrade -y

Cause: The upstream Termux maintainers describe the Google Play build as an experimental branch with missing functionality and bugs (see github.com/termux/termux-app); they recommend F-Droid or their GitHub releases for most users. If you have package-source or version issues with the Google Play build, switching to F-Droid or GitHub releases is the upstream-recommended fix.


PDF reading fails ("pdftoppm is not installed")

You see:

pdftoppm is not installed

The Read tool returns this error when you try to read a .pdf file, even after running pkg install poppler.

Fix: Create a which shim. Termux doesn't ship a which binary, and Claude Code's PDF reader uses which pdftoppm to detect the tool:

pkg install poppler
cat > $PREFIX/bin/which << 'SCRIPT'
#!/usr/bin/env bash
for arg in "$@"; do
  command -v "$arg"
done
SCRIPT
chmod +x $PREFIX/bin/which

After this, Read on PDF files works correctly.

Alternatively, pdftotext (included with poppler) works without the shim if you only need text extraction: pdftotext file.pdf -

Cause: Termux uses command -v rather than which for command detection, but Claude Code's PDF reader calls which pdftoppm directly. Without a which binary on PATH, the check fails and the reader refuses to proceed, even if pdftoppm is installed.


claude doctor crashes

You see:

Raw mode is not supported on the current process.stdin

claude doctor outputs this error and exits without completing any checks.

Fix (not yet confirmed on all devices): Run claude doctor in a direct interactive Termux session. Not backgrounded, not piped, not launched from within Claude Code itself:

# Open a fresh Termux terminal session and run:
claude doctor

If you are running it from inside a Claude Code session via the Bash tool, that won't work. The Bash tool does not provide a raw-mode terminal.

Cause: The claude doctor command uses the Ink library (a React-based terminal UI renderer) to render its output. Ink requires raw mode stdin. Termux provides raw mode in interactive sessions but not in piped or backgrounded contexts.

Status: Cosmetic. claude doctor is a diagnostic tool, not required for normal Claude Code operation. This repo ships an equivalent set of Termux/Android-specific checks as a bash script: scripts/check-termux-env.sh.


Upstream Issues

Known issues filed against the Claude Code repository that affect Android/Termux users:

IssueDescriptionStatusNotes
#502702.1.113+ broken on Termux/Android: native binary requires glibc, no JS fallbackOpenv2.9.0 install in this repo runs the linux-arm64 native binary directly under Termux's glibc-runner; see README
#16615Platform detection: android not recognizedClosed (not planned)Historic. Path A v2.9.0 ships the linux-arm64 binary which reports process.platform === 'linux', sidestepping the detection problem entirely
#13021Missing arm64-android ripgrep binaryClosedv2.x / pinned 2.1.112: symlink system rg onto vendor/ripgrep/arm64-android/rg (scripts/fix-ripgrep.sh); the env var does not redirect the search on 2.1.112. v2.9.0 install: not applicable. The linux-arm64 binary ships vendor/ripgrep/arm64-linux/rg and uses it

ADB Wireless Debugging

Setting up ADB over WiFi (pairing errors, dropped connections after screen lock or reboot, and reconnecting) has its own guide: see adb-wireless.md, which includes a Troubleshooting section for the common pairing and connection problems.


Path C: AVF (Experimental)

Android Virtualization Framework (AVF): Experimental, Tested on Pixel

Android 16 introduced a built-in Linux VM via the Android Virtualization Framework (AVF), and Android 17 expanded the Terminal app's user-facing controls. Claude Code has been installed and used for real work inside an AVF VM on Pixel 6 and Pixel 10 Pro running Android 17 (verified 2026-05-26). Path C is experimental.

See avf-guide.md for the full setup checklist, the Settings UI walkthrough, ADB hardware bridge, security defaults, and comparison with Path A and Path B.

Inside AVF the official Anthropic installer runs on a real Linux kernel, so the Path A crash and self-heal rollback covered at the top of this guide do not apply here. If a newer claude ever fails to launch in the VM, reinstall with the official installer (curl -fsSL https://claude.ai/install.sh | bash) or pin an earlier release.

What works:

  • Claude Code installs via the official Anthropic installer (curl -fsSL https://claude.ai/install.sh | bash) and completed real tasks during my testing
  • process.platform === "linux" (no platform detection issues)
  • Native /tmp inside the VM
  • Standard apt package management, including systemd updates
  • Memory size, Display resolution, and Keep awake are configurable in the Terminal app's Settings (gear icon) under Advanced
  • ADB wireless debugging from inside the VM provides access to phone sensors, GPS, camera, screenshots, screen recording, input injection, battery state, and WiFi info
  • Audio playback and recording (PulseAudio + VirtIO SoundCard)
  • Headless GUI rendering (Firefox screenshots, automated browser tasks)

What doesn't work well (yet):

  • Screen-off and Activity recreation: Android 17 mitigates screen-off VM kills with Settings > Advanced > Keep awake (timer up to 1 day, with a battery-life warning in the dialog). The Terminal Activity can still be recreated by some Android lifecycle events (configuration changes, accessibility services). Run long workloads under tmux or nohup to survive Activity recreation.
  • SysV IPC (inter-process communication) disabled in kernel: fio and some Python multiprocessing features do not work (CONFIG_SYSVIPC not set). Hard wall.
  • nftables non-functional: use iptables-legacy instead
  • apt upgrade can hang: whiptail text-based UI (TUI) dialogs block non-interactive sessions. Use DEBIAN_FRONTEND=noninteractive or kill the blocking process.
  • "VM damaged" on unclean shutdown: requires the Recovery > Reset to initial version flow and a re-download of the Debian image
  • Snapdragon phones not supported: Qualcomm exposes only protected VMs at EL2 (a privileged ARM processor level). Samsung's One UI 8.5 added Linux Terminal on Exynos S26 / S26+ per Samsung's release notes; not lab-verified here.
  • No Termux API access: camera, text-to-speech (TTS), clipboard, GPS, SMS, sensors are not available inside the VM natively (partial access via ADB bridge)
  • No kernel module loading: monolithic kernel, /lib/modules/ is empty
  • Copy-paste unreliable: multi-line commands break when pasted into the Terminal app

Security note: The stock Android Terminal VM ships with convenience-oriented defaults out of the box: a known default password for the droid user, passwordless sudo, and no firewall (SSH is installed but not running). These are Android's own defaults, not settings this project chose. See the AVF guide's Security Defaults section for the details and hardening steps.

Networking fix: Cellular data requires enabling "Unrestricted mobile data usage" in the Terminal app's settings, then restarting the device. WiFi works without this step. (Google Issue Tracker #402523629)

Using AVF? The AVF guide has the full setup checklist and the current known-issues list.


Last updated: 2026-07-01.