Skip to content

fix: respect $TMPDIR and os.tmpdir() instead of hardcoding /tmp/claude - #31701

Closed
rios0rios0 wants to merge 1 commit into
anthropics:mainfrom
rios0rios0:claude/fix-termux-tmpdir-paths-UTMsL
Closed

rios0rios0 wants to merge 1 commit into
anthropics:mainfrom
rios0rios0:claude/fix-termux-tmpdir-paths-UTMsL

Conversation

@rios0rios0

@rios0rios0 rios0rios0 commented Mar 7, 2026 •

Copy link
Copy Markdown

Add patch script and documentation to fix hardcoded /tmp/claude paths that break Claude Code on Termux/Android and other restricted environments where /tmp is not writable.

The patch script (scripts/patch-tmp-paths.sh) replaces 6 hardcoded /tmp references in cli.js with dynamic resolution using the same priority chain as Node.js os.tmpdir(): $TMPDIR > $TEMP > $TMP > /tmp.

Affected operations now fixed:

  • Main temp directory resolution (session data, scratchpads)
  • Sandbox TMPDIR environment variable
  • Shell command sandbox temp directory
  • Screenshot capture paths
  • MCP browser bridge socket directory
  • Sandbox path allowlist

Also includes:

  • scripts/termux-setup.sh for guided Termux setup
  • README.md section documenting Termux/Android usage

Related Issues

Fixes #15628, fixes #15637, fixes #16955, fixes #17366, fixes #18342, relates to #23634

These issues collectively have 20+ upvotes and 8+ duplicates, affecting all Termux/Android users since December 2025. All were auto-closed by the stale bot without being fixed.

Add patch script and documentation to fix hardcoded /tmp/claude paths
that break Claude Code on Termux/Android and other restricted environments
where /tmp is not writable.

The patch script (scripts/patch-tmp-paths.sh) replaces 6 hardcoded /tmp
references in cli.js with dynamic resolution using the same priority chain
as Node.js os.tmpdir(): $TMPDIR > $TEMP > $TMP > /tmp.

Affected operations now fixed:
- Main temp directory resolution (session data, scratchpads)
- Sandbox TMPDIR environment variable
- Shell command sandbox temp directory
- Screenshot capture paths
- MCP browser bridge socket directory
- Sandbox path allowlist

Also includes:
- scripts/termux-setup.sh for guided Termux setup
- README.md section documenting Termux/Android usage

Fixes #15628, fixes #15637, fixes #16955, fixes #17366, fixes #18342
Relates to #23634

https://claude.ai/code/session_013FEYM2EYhLJeVEZZbmFTqD
Copilot AI review requested due to automatic review settings March 7, 2026 04:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Termux/Android support guidance and helper scripts to avoid failures caused by hardcoded /tmp/claude paths in restricted environments (e.g., Termux where /tmp isn’t writable).

Changes:

  • Add scripts/patch-tmp-paths.sh to patch bundled cli.js hardcoded /tmp references to dynamic temp-dir resolution.
  • Add scripts/termux-setup.sh to configure a Termux-compatible temp directory and optionally run the patch.
  • Document Termux/Android setup steps in README.md.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
scripts/termux-setup.sh Guided Termux setup: chooses writable tmp base, updates shell profile, optionally runs patcher.
scripts/patch-tmp-paths.sh Patches specific string patterns in cli.js to use env-driven temp directory selection.
README.md Adds Termux/Android usage instructions and patch command guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md

**Quick setup:**
```bash
bash scripts/termux-setup.sh

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Quick setup” command assumes the user has a checkout of this repository (and that scripts/termux-setup.sh exists locally). Given the earlier install instructions recommend curl/homebrew/winget, it would help to clarify that Termux users should clone the repo (or provide a curl/wget invocation to the raw script).

Suggested change
bash scripts/termux-setup.sh
# Option 1: run directly from GitHub (no local checkout required)
curl -fsSL https://raw.githubusercontent.com/anthropics/claude-code/HEAD/scripts/termux-setup.sh | bash
# Option 2: from a cloned repo
# git clone https://github.com/anthropics/claude-code.git
# cd claude-code
# bash scripts/termux-setup.sh

Copilot uses AI. Check for mistakes.
Comment thread README.md

**Manual setup** — add this to your shell profile (`~/.bashrc` or `~/.zshrc`):
```bash
export CLAUDE_CODE_TMPDIR="$TMPDIR"

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual setup exports CLAUDE_CODE_TMPDIR="$TMPDIR", but on some shells/environments TMPDIR may be unset. Consider documenting a safe fallback (e.g., using ${TMPDIR:-/tmp} or deriving it from Node’s os.tmpdir()), so users don’t accidentally set the variable to an empty string.

Suggested change
export CLAUDE_CODE_TMPDIR="$TMPDIR"
export CLAUDE_CODE_TMPDIR="${TMPDIR:-/tmp}"

Copilot uses AI. Check for mistakes.
Comment thread README.md

**If you still encounter `/tmp/claude` permission errors**, run the patch script to fix hardcoded paths in the bundled `cli.js`:
```bash
bash scripts/patch-tmp-paths.sh "$(command -v claude | xargs dirname)/../lib/node_modules/@anthropic-ai/claude-code/cli.js"

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patch command uses command -v claude | xargs dirname; using dirname "$(command -v claude)" is simpler and avoids xargs quirks (e.g., unexpected splitting) in case the path contains whitespace.

Suggested change
bash scripts/patch-tmp-paths.sh "$(command -v claude | xargs dirname)/../lib/node_modules/@anthropic-ai/claude-code/cli.js"
bash scripts/patch-tmp-paths.sh "$(dirname "$(command -v claude)")/../lib/node_modules/@anthropic-ai/claude-code/cli.js"

Copilot uses AI. Check for mistakes.
Comment thread scripts/termux-setup.sh
echo "Warning: This does not appear to be a Termux environment."
echo "This script is designed for Termux on Android."
echo ""
read -rp "Continue anyway? (y/N) " answer

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the script runs with set -e, the read -rp prompt will cause the script to exit with an error in non-interactive contexts (e.g., when run via a wrapper with stdin closed). Consider only prompting when stdin is a TTY (e.g., [[ -t 0 ]]), or make the read non-fatal and default to "no" when input isn’t available.

Suggested change
read -rp "Continue anyway? (y/N) " answer
if [[ -t 0 ]]; then
read -rp "Continue anyway? (y/N) " answer
else
answer="n"
fi

Copilot uses AI. Check for mistakes.
Comment thread scripts/termux-setup.sh
Comment on lines +77 to +83
if [ -f "$PROFILE" ] && grep -qF "CLAUDE_CODE_TMPDIR" "$PROFILE"; then
echo " [SKIP] CLAUDE_CODE_TMPDIR already set in $PROFILE"
else
echo "" >> "$PROFILE"
echo "# Claude Code — use Termux-compatible temp directory" >> "$PROFILE"
echo "$EXPORT_LINE" >> "$PROFILE"
echo " [OK] Added to $PROFILE:"

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The profile check grep -qF "CLAUDE_CODE_TMPDIR" will skip updating even if the variable is commented out or set to a different value. To avoid leaving users misconfigured, consider checking for the exact export line you intend to add, or updating/replacing an existing export CLAUDE_CODE_TMPDIR=... entry.

Suggested change
if [ -f "$PROFILE" ] && grep -qF "CLAUDE_CODE_TMPDIR" "$PROFILE"; then
echo " [SKIP] CLAUDE_CODE_TMPDIR already set in $PROFILE"
else
echo "" >> "$PROFILE"
echo "# Claude Code — use Termux-compatible temp directory" >> "$PROFILE"
echo "$EXPORT_LINE" >> "$PROFILE"
echo " [OK] Added to $PROFILE:"
if [ -f "$PROFILE" ]; then
if grep -qE '^[[:space:]]*export[[:space:]]+CLAUDE_CODE_TMPDIR=' "$PROFILE"; then
# Replace existing export with the new value
sed -i.bak -E 's|^[[:space:]]*export[[:space:]]+CLAUDE_CODE_TMPDIR=.*$|'"$EXPORT_LINE"'|' "$PROFILE"
echo " [OK] Updated CLAUDE_CODE_TMPDIR in $PROFILE"
elif grep -q "CLAUDE_CODE_TMPDIR" "$PROFILE"; then
# Variable mentioned only in comments or non-export lines; append a correct export
echo "" >> "$PROFILE"
echo "# Claude Code — use Termux-compatible temp directory" >> "$PROFILE"
echo "$EXPORT_LINE" >> "$PROFILE"
echo " [OK] Added CLAUDE_CODE_TMPDIR export to $PROFILE (existing commented reference found)"
echo " $EXPORT_LINE"
else
echo "" >> "$PROFILE"
echo "# Claude Code — use Termux-compatible temp directory" >> "$PROFILE"
echo "$EXPORT_LINE" >> "$PROFILE"
echo " [OK] Added CLAUDE_CODE_TMPDIR export to $PROFILE:"
echo " $EXPORT_LINE"
fi
else
echo "" >> "$PROFILE"
echo "# Claude Code — use Termux-compatible temp directory" >> "$PROFILE"
echo "$EXPORT_LINE" >> "$PROFILE"
echo " [OK] Created $PROFILE and added CLAUDE_CODE_TMPDIR export:"

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +130
(
"MCP browser bridge dir (jd6)",
"return`/tmp/claude-mcp-browser-bridge-",
'return`${process.env.TMPDIR||process.env.TEMP||process.env.TMP||"/tmp"}/claude-mcp-browser-bridge-',
),

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment says the patched resolution prioritizes $CLAUDE_CODE_TMPDIR, but this patch uses only $TMPDIR/$TEMP/$TMP (ignoring process.env.CLAUDE_CODE_TMPDIR). If a user sets only CLAUDE_CODE_TMPDIR (as suggested elsewhere), the MCP browser bridge path will still fall back to /tmp. Consider including process.env.CLAUDE_CODE_TMPDIR|| ahead of the TMPDIR chain for these replacements.

Copilot uses AI. Check for mistakes.
(
"Sandbox path allowlist ($x6)",
'"/tmp/claude","/private/tmp/claude"',
f'"/tmp/claude","/private/tmp/claude",{TMPDIR}+"/claude"',

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sandbox allowlist patch adds (process.env.TMPDIR||process.env.TEMP||process.env.TMP||"/tmp")+"/claude", but does not include process.env.CLAUDE_CODE_TMPDIR. If the runtime override is set via CLAUDE_CODE_TMPDIR, the allowlist still won’t include that path. Consider using process.env.CLAUDE_CODE_TMPDIR||… here to match the documented priority chain.

Suggested change
f'"/tmp/claude","/private/tmp/claude",{TMPDIR}+"/claude"',
f'"/tmp/claude","/private/tmp/claude",(process.env.CLAUDE_CODE_TMPDIR||{TMPDIR})+"/claude"',

Copilot uses AI. Check for mistakes.
Comment on lines +173 to +175
if content != original:
with open(cli_js_path, "w", encoding="utf-8") as f:
f.write(content)

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patch writes changes back to cli.js in-place without creating a backup or using an atomic write. If the process is interrupted (or the filesystem fills up), this can leave users with a corrupted cli.js. Consider writing to a temporary file and renaming, and/or creating a .bak copy before modifying.

Copilot uses AI. Check for mistakes.
@rios0rios0

Copy link
Copy Markdown
Author

Workaround: Claude Code on Termux/Android

I've been running Claude Code on Termux (Android) and hit two blocking incompatibilities. I put together a workaround that patches both issues automatically on every shell startup, surviving npm update cycles. Sharing here in case it helps others while an official fix is pending.

The Problems

1. Hardcoded /tmp paths in cli.js

Claude Code's bundled cli.js hardcodes "/tmp" and "/tmp/claude" in multiple places (tmpdir resolver, sandbox env, file allowlists, MCP browser bridge, task spawn, screenshot paths). On Termux, /tmp is owned by shell:shell with mode 0771 — app processes cannot write there. This causes EACCES: permission denied, mkdir '/tmp/claude-<PID>' on every Bash tool invocation.

The CLAUDE_CODE_TMPDIR env var exists but is inconsistently applied — many code paths ignore it entirely.

2. Missing vendored ripgrep for arm64-android

Claude Code ships platform-specific ripgrep binaries under vendor/ripgrep/<platform>/rg, but does not include arm64-android (Termux's platform identifier). This causes spawn .../vendor/ripgrep/arm64-android/rg ENOENT, breaking the Grep and Glob tools completely.

The Solution

Three files, two of which are standard shell configuration (~/.zshenv and ~/.zshrc) and one is a patch script they source:

File 1 — ~/.zshenv (sourced for ALL zsh invocations, including non-interactive shells spawned by Claude Code's Bash tool)

Add this block before any PATH setup:

# On Termux/Android, /tmp is owned by shell:shell (0771) and not writable by app processes.
# TMPDIR must be set before any tool (Node.js, Claude Code, etc.) calls os.tmpdir(),
# which defaults to /tmp on Linux when TMPDIR is unset.
if [[ -n "$PREFIX" && -d "$PREFIX/tmp" ]]; then
    export TMPDIR="${TMPDIR:-$PREFIX/tmp}"
fi

This ensures TMPDIR is set to Termux's writable temp directory ($PREFIX/tmp) for every shell context. It must be in ~/.zshenv rather than ~/.zshrc because Claude Code spawns non-interactive shells that don't source ~/.zshrc.

File 2 — ~/.zshrc (add this in the Android-specific section)

# Workaround: Claude Code hardcodes /tmp paths which are not writable on Termux.
# This script patches cli.js to use process.env.TMPDIR and symlinks system ripgrep.
# See: https://github.com/anthropics/claude-code/issues/15637
source "$HOME/.scripts/android-patch-claude-code-tmpdir.sh"

File 3 — ~/.scripts/android-patch-claude-code-tmpdir.sh (the actual patch script)

This is the core of the workaround. It:

  1. Reverts any old require("os")-based patches — an earlier approach used require("os").tmpdir(), but cli.js is an ESM bundle where require is not defined.
  2. Replaces hardcoded /tmp paths with (process.env.TMPDIR||"/tmp") using 6 version-resilient sed patterns that match code structure (||"/tmp", :"/tmp"), `/tmp/ in template literals, etc.) rather than minified variable names, so they survive version updates without manual maintenance.
  3. Symlinks the system ripgrep (pkg install ripgrep) into Claude Code's vendor/ripgrep/arm64-android/rg directory.
  4. Is idempotent — uses a sentinel comment (__TERMUX_TMPDIR_PATCHED__) to skip re-patching.

The full script is available in my dotfiles repository:
https://github.com/rios0rios0/dotfiles/blob/main/dot_scripts/android-patch-claude-code-tmpdir.sh

The other two files (note: my dotfiles use chezmoi, so dot_zshenv maps to ~/.zshenv and dot_zshrc.tmpl maps to ~/.zshrc):

Prerequisites

pkg install ripgrep  # required for the Grep/Glob tools fix

After Updating Claude Code

When you run npm update -g @anthropic-ai/claude-code, the patched cli.js gets replaced. Simply open a new shell — the patch script in ~/.zshrc will detect the missing sentinel and re-apply automatically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants