Skip to content

[BUG] CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 leaves an empty ~/.bash_profile behind, silently breaking login-shell PATH #76236

Closed as not planned

Description

@mhsong55

Preflight Checklist

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

What's Wrong?

With CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, Claude Code creates empty 0-byte files in $HOME at startup — one for each path in a fixed list of shell and package-manager dotfiles that does not already exist. It never removes them, and deleting one then running again recreates it.

The damaging one is ~/.bash_profile.

Bash login shells read only the first of ~/.bash_profile → ~/.bash_login → ~/.profile. Of those three, a stock Debian/Ubuntu account has only ~/.profile — /etc/skel ships .profile, .bashrc and .bash_logout, and no .bash_profile. So for as long as the empty ~/.bash_profile exists, no login shell reads ~/.profile: ~/.local/bin and ~/bin drop out of PATH, ~/.bashrc is not sourced, and anything else ~/.profile loads (~/.cargo/env, for instance) is skipped. On my machine this also removed claude itself from the login-shell PATH, since the native installer puts it in ~/.local/bin.

This is distinct from the project-directory pollution of #17087 and #51405. This is $HOME, so the symptom is not a dirty git status but a quietly altered environment for every login shell on the account.

No sandbox opt-in is involved. Per the sandboxing docs, sandbox.credentials "affects sandboxed Bash commands only", while CLAUDE_CODE_SUBPROCESS_ENV_SCRUB strips credentials "from all subprocesses regardless of sandboxing". I never set sandbox.enabled anywhere.

What Should Happen?

Claude Code should not create the user's shell and toolchain dotfiles.

~/.bash_profile above all. It is not clutter but semantics: its mere existence stops every login shell on the account from reading ~/.profile. It should never be created. The other ten files, and the .pip and .config/pip directories, are empty and inert, but they should not be left behind either.

Removing them at exit would not be enough. I checked while a run was still in flight: ~/.bash_profile is already on disk, and a login shell started at that moment has already lost ~/.profile. An interactive session lasts hours, and a crash or kill -9 would leave the file behind for good.

Error Messages/Logs

None. Nothing is printed, prompted, or logged when the files are created.

Steps to Reproduce

Deterministic, and no authentication is needed: the run fails at the API-key check after the files are written. bubblewrap is installed on my machine; I did not test a host without it.

env -i clears PATH, so resolve the binary first and call it by absolute path.

CLAUDE=$(readlink -f "$(command -v claude)")   # e.g. ~/.local/share/claude/versions/2.1.205

# 1. A home dir shaped like a stock Debian/Ubuntu account: ~/.profile, no ~/.bash_profile
rm -rf /tmp/fakehome && mkdir -p /tmp/fakehome/proj /tmp/fakehome/.local/bin
printf 'export PATH="$HOME/.local/bin:$PATH"\n' > /tmp/fakehome/.profile

# 2. BEFORE — the login shell reads ~/.profile
env -i HOME=/tmp/fakehome bash -lc 'echo "$PATH"'      # contains ~/.local/bin

# 3. Run Claude Code once with the variable set
cd /tmp/fakehome/proj
env -i HOME=/tmp/fakehome PATH=/usr/bin:/bin ANTHROPIC_API_KEY=sk-invalid \
  CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 \
  "$CLAUDE" -p 'hi' --max-turns 1 </dev/null

# 4. A file that did not exist now does; the one that did exist is untouched
wc -c /tmp/fakehome/.bash_profile    # 0
wc -c /tmp/fakehome/.profile         # unchanged

# 5. AFTER — the login shell no longer reads ~/.profile
env -i HOME=/tmp/fakehome bash -lc 'echo "$PATH"'      # ~/.local/bin is gone

# 6. Removing the stub restores it
rm /tmp/fakehome/.bash_profile
env -i HOME=/tmp/fakehome bash -lc 'echo "$PATH"'      # back to normal

Running the same block without CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 adds only .claude and .claude.json, and steps 2 and 5 print the same PATH.

On a home directory where none of them exist yet, the run creates these 11 files — all 0 bytes — and 2 directories:

.bash_aliases  .bash_profile  .bashrc     .bunfig.toml  .gitconfig
.netrc         .npmrc         .profile    .yarnrc       .yarnrc.yml
.zshrc         .config/       .pip/

~/.profile is on that list, which is why step 1 seeds it: because it already exists, it is left alone, and wc -c shows its byte count unchanged. Existing dotfiles keep their contents and their mtimes. Only the files you did not have get created — which is why this goes unnoticed on a real machine.

Claude Model

Not sure / Multiple models

Is this a regression?

No, this never worked

Last Working Version

None

Claude Code Version

2.1.205

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Claude Code names these files itself. Run it with the variable set inside a git repository, and it appends this to .git/info/exclude:

# claude-code scrub-mode stubs
/bunfig.toml
/package.json
/.npmrc
/.yarnrc
...

So for the project directory the stubs are deliberately kept out of git status — the side effect is recognised and handled there. Nothing equivalent happens for $HOME, where the same mechanism drops ~/.bash_profile.

Two more things from that same file suggest nothing tracks what was created: the block is appended again on every run (the .git/info/exclude in my repo now contains it twice), and the stubs in $HOME are never removed.

Versions. I ran the reproducer against every build I still have installed — 2.1.198, 2.1.199, 2.1.201, 2.1.202, 2.1.204 and 2.1.205 — and all six create ~/.bash_profile.

Environment details. Native installer, ~/.local/bin/claude → ~/.local/share/claude/versions/. Kernel 6.8.0-117-generic. Terminal is Ghostty running tmux, shell is bash. bubblewrap 0.9.0-1ubuntu0.1 (behaviour on a host without it: not tested). sandbox was never enabled in any settings file. None of this matters to the bug — the reproducer above is non-interactive and writes the files before any terminal state is involved.

Workaround. Make ~/.bash_profile source ~/.profile. I verified this survives: after another Claude Code run the file's contents are unchanged, so the line stays.

if [ -f "$HOME/.profile" ]; then . "$HOME/.profile"; fi

Related.

Activity

  1. github-actions commented on Sep 1, 2026

    @github-actions

    Closing for now — inactive for too long. Please open a new issue if this is still relevant.

  2. marcindulak commented on Sep 2, 2026

    @marcindulak

    This issue was incorrectly closed by the github-actions bot, despite having reproduction steps and Claude version. This behavior of the bot is tracked in #87647. Consider upvoting that issue for visibilty, maybe the bot behavior gets improved.

  3. github-actions commented on Oct 24, 2026

    @github-actions

    This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.

  4. locked as resolved and limited conversation to collaborators on Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sandboxbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:linuxIssue specifically occurs on Linux

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions