fix: stop creating ~/.bash_profile — was destroying system PATH (#1258)

On Ubuntu/Debian, ~/.bash_profile doesn't exist by default. When bash
starts as a login shell (bash -l), it sources the FIRST file it finds
from: ~/.bash_profile, ~/.bash_login, ~/.profile. Since only ~/.profile
exists, that's what gets sourced — and ~/.profile sets up the standard
PATH (/usr/bin, /bin, etc.) and sources ~/.bashrc.

Our inject_env_vars_* functions and _finalize_claude_install were writing
to ~/.bash_profile and ~/.zprofile (either via touch+append or via
for-loop over all rc files). Creating ~/.bash_profile caused bash -l to
source it INSTEAD of ~/.profile, completely losing the standard PATH
setup. After deployment, even basic commands like `ls` would fail.

Fix: Only write to ~/.profile, ~/.bashrc, ~/.zshrc across all clouds
(shared, fly, sprite). These are the standard files that work correctly
on all Linux distros without breaking the shell initialization chain.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-16 00:27:28 -08:00 committed by GitHub
parent 99b21e2797
commit 46e6f46008
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 21 additions and 24 deletions

View file

@ -156,7 +156,7 @@ inject_env_vars_local mock_upload mock_run "MY_KEY=my_value"
// inject_env_vars_local does NOT pass server_ip - upload gets (local_path, remote_path)
expect(result.stdout).toContain("UPLOAD_ARGS:");
expect(result.stdout).toContain("/tmp/env_config");
expect(result.stdout).toContain("for rc in ~/.profile ~/.bash_profile ~/.bashrc ~/.zshrc ~/.zprofile; do cat /tmp/env_config >>");
expect(result.stdout).toContain("cat /tmp/env_config >> ~/.profile && cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc");
});
it("should generate correct env config content", () => {

View file

@ -378,9 +378,10 @@ inject_env_vars_fly() {
generate_env_config "$@" > "${env_temp}"
# Upload and append to .profile, .bash_profile, .bashrc, and .zshrc
# Upload and append to .profile, .bashrc, .zshrc ONLY.
# CRITICAL: Do NOT write to ~/.bash_profile or ~/.zprofile — see shared/common.sh.
upload_file "${env_temp}" "/tmp/env_config"
run_server "for rc in ~/.profile ~/.bash_profile ~/.bashrc ~/.zshrc ~/.zprofile; do cat /tmp/env_config >> \"\$rc\"; done && rm /tmp/env_config"
run_server "cat /tmp/env_config >> ~/.profile && cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
# Note: temp file will be cleaned up by trap handler
}

View file

@ -1090,10 +1090,10 @@ inject_env_vars_ssh() {
generate_env_config "$@" > "${env_temp}"
# Upload and append to .profile, .bash_profile, .bashrc, and .zshrc
# bash -l sources the FIRST of ~/.bash_profile, ~/.bash_login, ~/.profile
# Append to .profile, .bashrc, .zshrc only — NEVER create ~/.bash_profile
# (creating it makes bash -l skip ~/.profile, destroying the standard PATH)
"${upload_func}" "${server_ip}" "${env_temp}" "/tmp/env_config"
"${run_func}" "${server_ip}" "for rc in ~/.profile ~/.bash_profile ~/.bashrc ~/.zshrc ~/.zprofile; do cat /tmp/env_config >> \"\$rc\"; done && rm /tmp/env_config"
"${run_func}" "${server_ip}" "cat /tmp/env_config >> ~/.profile && cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
# Note: temp file will be cleaned up by trap handler
@ -1119,9 +1119,9 @@ inject_env_vars_local() {
generate_env_config "$@" > "${env_temp}"
# Upload and append to .profile, .bash_profile, .bashrc, and .zshrc
# Append to .profile, .bashrc, .zshrc only — never .bash_profile
"${upload_func}" "${env_temp}" "/tmp/env_config"
"${run_func}" "for rc in ~/.profile ~/.bash_profile ~/.bashrc ~/.zshrc ~/.zprofile; do cat /tmp/env_config >> \"\$rc\"; done && rm /tmp/env_config"
"${run_func}" "cat /tmp/env_config >> ~/.profile && cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
# Note: temp file will be cleaned up by trap handler
@ -1264,20 +1264,15 @@ install_claude_code() {
# Include fnm paths so node is found even in non-interactive SSH sessions
local claude_path='export PATH=$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$HOME/.local/share/fnm:$PATH; if command -v fnm >/dev/null 2>&1; then eval "$(fnm env)"; fi'
# Finalize installation: set up shell integration (PATH, completions)
# Persists claude and fnm PATH entries to .profile/.bashrc/.zshrc
# Finalize: set up shell integration and persist PATH to .profile/.bashrc/.zshrc.
# NEVER write to ~/.bash_profile — creating it breaks Ubuntu's login shell chain.
_finalize_claude_install() {
log_step "Setting up Claude Code shell integration..."
${run_cb} "${claude_path} && claude install --force" >/dev/null 2>&1 || true
# Write claude PATH to all shell configs so login shells find the binary.
# .bashrc has a non-interactive guard that skips appended exports when
# run via `ssh host "cmd"` or `bash -lc`, so .profile is essential.
# Write claude PATH to all shell configs so login shells find the binary.
# bash -l sources the FIRST of ~/.bash_profile, ~/.bash_login, ~/.profile
# so we must write to all of them to be safe.
${run_cb} "for rc in ~/.profile ~/.bash_profile ~/.bashrc ~/.zshrc ~/.zprofile; do touch \"\$rc\"; grep -q '.claude/local/bin' \"\$rc\" 2>/dev/null || printf '\\n# Claude Code PATH\\nexport PATH=\"\$HOME/.claude/local/bin:\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\"\\n' >> \"\$rc\"; done" >/dev/null 2>&1 || true
# Ensure fnm bootstrap is in all shell configs so new shells can find node
${run_cb} "if command -v fnm >/dev/null 2>&1 || test -d \$HOME/.local/share/fnm; then for rc in ~/.profile ~/.bash_profile ~/.bashrc ~/.zshrc ~/.zprofile; do grep -q 'fnm env' \"\$rc\" 2>/dev/null || printf '\\n# fnm (node version manager)\\nexport PATH=\"\$HOME/.local/share/fnm:\$PATH\"\\nif command -v fnm >/dev/null 2>&1; then eval \"\\\$(fnm env)\"; fi\\n' >> \"\$rc\"; done; fi" >/dev/null 2>&1 || true
# Write claude PATH to .profile (login shells), .bashrc, .zshrc
${run_cb} "for rc in ~/.profile ~/.bashrc ~/.zshrc; do grep -q '.claude/local/bin' \"\$rc\" 2>/dev/null || printf '\\n# Claude Code PATH\\nexport PATH=\"\$HOME/.claude/local/bin:\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\"\\n' >> \"\$rc\"; done" >/dev/null 2>&1 || true
# Ensure fnm bootstrap is in shell configs so new shells can find node
${run_cb} "if command -v fnm >/dev/null 2>&1 || test -d \$HOME/.local/share/fnm; then for rc in ~/.profile ~/.bashrc ~/.zshrc; do grep -q 'fnm env' \"\$rc\" 2>/dev/null || printf '\\n# fnm (node version manager)\\nexport PATH=\"\$HOME/.local/share/fnm:\$PATH\"\\nif command -v fnm >/dev/null 2>&1; then eval \"\\\$(fnm env)\"; fi\\n' >> \"\$rc\"; done; fi" >/dev/null 2>&1 || true
}
# Already installed?
@ -1381,7 +1376,7 @@ inject_env_vars_cb() {
generate_env_config "$@" > "${env_temp}"
${upload_cb} "${env_temp}" "/tmp/env_config"
${run_cb} "for rc in ~/.profile ~/.bash_profile ~/.bashrc ~/.zshrc ~/.zprofile; do cat /tmp/env_config >> \"\$rc\"; done && rm /tmp/env_config"
${run_cb} "cat /tmp/env_config >> ~/.profile && cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
# Offer optional GitHub CLI setup
offer_github_auth "${run_cb}"

View file

@ -178,8 +178,8 @@ setup_shell_environment() {
export PATH="${HOME}/.bun/bin:/.sprite/languages/bun/bin:${PATH}"
EOF
# Upload and append to shell configs
sprite exec -s "${sprite_name}" -file "${path_temp}:/tmp/path_config" -- bash -c "cat /tmp/path_config >> ~/.zprofile && cat /tmp/path_config >> ~/.zshrc && rm /tmp/path_config"
# Upload and append to .profile and .zshrc ONLY (not .zprofile)
sprite exec -s "${sprite_name}" -file "${path_temp}:/tmp/path_config" -- bash -c "cat /tmp/path_config >> ~/.profile && cat /tmp/path_config >> ~/.zshrc && rm /tmp/path_config"
# Switch bash to zsh
local bash_temp
@ -209,8 +209,9 @@ inject_env_vars_sprite() {
generate_env_config "$@" > "${env_temp}"
# Upload and append to .profile, .bash_profile, .bashrc, and .zshrc using sprite exec
sprite exec -s "${sprite_name}" -file "${env_temp}:/tmp/env_config" -- bash -c "for rc in ~/.profile ~/.bash_profile ~/.bashrc ~/.zshrc ~/.zprofile; do cat /tmp/env_config >> \"\$rc\"; done && rm /tmp/env_config"
# Upload and append to .profile, .bashrc, .zshrc ONLY using sprite exec.
# CRITICAL: Do NOT write to ~/.bash_profile or ~/.zprofile — see shared/common.sh.
sprite exec -s "${sprite_name}" -file "${env_temp}:/tmp/env_config" -- bash -c "cat /tmp/env_config >> ~/.profile && cat /tmp/env_config >> ~/.bashrc && cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_config"
trap - EXIT
# Offer optional GitHub CLI setup