Shell sessions losing $PATH value from parent shell #3246

Open
opened 2026-02-16 17:39:18 -05:00 by yindo · 4 comments
Owner

Originally created by @Walid-Azur on GitHub (Dec 1, 2025).

Originally assigned to: @rekram1-node on GitHub.

Issue: Shell Sessions Losing $PATH Value

Summary

Shell commands executed via the Bash tool receive a minimal system PATH instead of inheriting the full parent shell environment. This causes commands to fail when they depend on tools installed in custom paths (homebrew, nvm, conda, etc.).

Reproduction

Current Behavior

# Parent shell PATH (current Claude Code session)
$ echo $PATH
/Users/optron/.opencode/bin:/Users/optron/.antigravity/antigravity/bin:/Users/optron/.npm-global/bin:/Users/optron/.config/iterm2:/Users/optron/.bun/bin:...
# (40+ paths)

# OpenCode spawned shell PATH
$ opencode run --model openrouter/qwen/qwen3-coder:free "Show me your \$PATH value by running echo \$PATH"
/Users/optron/.local/bin:/Users/optron/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
# (Only 9 minimal system paths)

Missing Paths

When OpenCode spawns shell sessions, the following paths are lost:

  • ~/.opencode/bin
  • ~/.bun/bin
  • ~/.npm-global/bin
  • Node version managers (nvm, fnm)
  • Language managers (conda, rbenv, pyenv)
  • Development tools (flutter, android-sdk, docker)
  • Custom user paths from .zshrc/.bashrc

Root Cause

Location: packages/opencode/src/tool/bash.ts:216-224

const proc = spawn(params.command, {
  shell,
  cwd: Instance.directory,
  env: {
    ...process.env,  // This spreads the env, but...
  },
  stdio: ["ignore", "pipe", "pipe"],
  detached: process.platform !== "win32",
})

Problem: While process.env is spread correctly, spawn() with shell: "/bin/zsh" (or bash) creates a non-interactive, non-login shell that doesn't source initialization files (.zshrc, .bashrc, .zprofile).

This means:

  • Shell RC files are NOT sourced
  • Custom PATH modifications in RC files are NOT applied
  • Only the minimal system PATH is used

Impact

Commands that depend on tools in custom paths will fail:

  • node, npm, pnpm (if installed via nvm/volta)
  • python (if using conda/pyenv)
  • docker (if installed via Docker Desktop)
  • flutter, dart (custom SDK paths)
  • Any homebrew-installed tools
  • Project-specific toolchains

Proposed Solutions

Option 1: Launch Interactive Shell (Recommended)

Modify the command to source RC files:

const shellCommand = (() => {
  if (typeof shell === 'string' && shell.includes('zsh')) {
    return `/bin/zsh -i -c ${JSON.stringify(params.command)}`
  }
  if (typeof shell === 'string' && shell.includes('bash')) {
    return `/bin/bash -i -c ${JSON.stringify(params.command)}`
  }
  return params.command
})()

const proc = spawn(shellCommand, {
  shell: true,  // Let system handle shell selection
  cwd: Instance.directory,
  env: {
    ...process.env,
  },
  stdio: ["ignore", "pipe", "pipe"],
  detached: process.platform !== "win32",
})

Pros: Full PATH inheritance, matches user's shell environment
Cons: Slightly slower startup (RC file sourcing), potential side effects from RC files

Option 2: Explicit PATH Preservation

Explicitly preserve PATH from parent process:

const proc = spawn(params.command, {
  shell,
  cwd: Instance.directory,
  env: {
    ...process.env,
    PATH: process.env.PATH,  // Explicitly preserve PATH
  },
  stdio: ["ignore", "pipe", "pipe"],
  detached: process.platform !== "win32",
})

Pros: Simple, no RC file sourcing overhead
Cons: Misses other env vars set in RC files (JAVA_HOME, etc.)

Option 3: Login Shell Flag

Use login shell flag:

const shellPath = typeof shell === 'string' ? shell : '/bin/bash'
const proc = spawn(params.command, {
  shell: `${shellPath} -l`,  // Login shell
  cwd: Instance.directory,
  env: {
    ...process.env,
  },
  stdio: ["ignore", "pipe", "pipe"],
  detached: process.platform !== "win32",
})

Pros: Sources login profile files
Cons: May not source all interactive RC settings

Testing

To verify fix:

# Test 1: PATH inheritance
opencode run "echo \$PATH" | wc -l
# Should show many paths, not just system defaults

# Test 2: Custom tools available
opencode run "which node && which docker && which bun"
# Should find tools in custom paths

# Test 3: Environment variables
opencode run "env | grep -E '(NVM_DIR|CONDA_|HOMEBREW_)'"
# Should show custom env vars from RC files

Additional Context

  • Platform: macOS (Darwin 25.1.0)
  • Shell: zsh (default on macOS)
  • OpenCode Version: Latest dev branch
  • Related Code: packages/opencode/src/tool/bash.ts

References

Originally created by @Walid-Azur on GitHub (Dec 1, 2025). Originally assigned to: @rekram1-node on GitHub. # Issue: Shell Sessions Losing $PATH Value ## Summary Shell commands executed via the Bash tool receive a minimal system PATH instead of inheriting the full parent shell environment. This causes commands to fail when they depend on tools installed in custom paths (homebrew, nvm, conda, etc.). ## Reproduction ### Current Behavior ```bash # Parent shell PATH (current Claude Code session) $ echo $PATH /Users/optron/.opencode/bin:/Users/optron/.antigravity/antigravity/bin:/Users/optron/.npm-global/bin:/Users/optron/.config/iterm2:/Users/optron/.bun/bin:... # (40+ paths) # OpenCode spawned shell PATH $ opencode run --model openrouter/qwen/qwen3-coder:free "Show me your \$PATH value by running echo \$PATH" /Users/optron/.local/bin:/Users/optron/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin # (Only 9 minimal system paths) ``` ### Missing Paths When OpenCode spawns shell sessions, the following paths are lost: - `~/.opencode/bin` - `~/.bun/bin` - `~/.npm-global/bin` - Node version managers (nvm, fnm) - Language managers (conda, rbenv, pyenv) - Development tools (flutter, android-sdk, docker) - Custom user paths from `.zshrc`/`.bashrc` ## Root Cause **Location**: `packages/opencode/src/tool/bash.ts:216-224` ```typescript const proc = spawn(params.command, { shell, cwd: Instance.directory, env: { ...process.env, // This spreads the env, but... }, stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32", }) ``` **Problem**: While `process.env` is spread correctly, `spawn()` with `shell: "/bin/zsh"` (or bash) creates a **non-interactive, non-login shell** that doesn't source initialization files (`.zshrc`, `.bashrc`, `.zprofile`). This means: - Shell RC files are NOT sourced - Custom PATH modifications in RC files are NOT applied - Only the minimal system PATH is used ## Impact Commands that depend on tools in custom paths will fail: - `node`, `npm`, `pnpm` (if installed via nvm/volta) - `python` (if using conda/pyenv) - `docker` (if installed via Docker Desktop) - `flutter`, `dart` (custom SDK paths) - Any homebrew-installed tools - Project-specific toolchains ## Proposed Solutions ### Option 1: Launch Interactive Shell (Recommended) Modify the command to source RC files: ```typescript const shellCommand = (() => { if (typeof shell === 'string' && shell.includes('zsh')) { return `/bin/zsh -i -c ${JSON.stringify(params.command)}` } if (typeof shell === 'string' && shell.includes('bash')) { return `/bin/bash -i -c ${JSON.stringify(params.command)}` } return params.command })() const proc = spawn(shellCommand, { shell: true, // Let system handle shell selection cwd: Instance.directory, env: { ...process.env, }, stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32", }) ``` **Pros**: Full PATH inheritance, matches user's shell environment **Cons**: Slightly slower startup (RC file sourcing), potential side effects from RC files ### Option 2: Explicit PATH Preservation Explicitly preserve PATH from parent process: ```typescript const proc = spawn(params.command, { shell, cwd: Instance.directory, env: { ...process.env, PATH: process.env.PATH, // Explicitly preserve PATH }, stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32", }) ``` **Pros**: Simple, no RC file sourcing overhead **Cons**: Misses other env vars set in RC files (JAVA_HOME, etc.) ### Option 3: Login Shell Flag Use login shell flag: ```typescript const shellPath = typeof shell === 'string' ? shell : '/bin/bash' const proc = spawn(params.command, { shell: `${shellPath} -l`, // Login shell cwd: Instance.directory, env: { ...process.env, }, stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32", }) ``` **Pros**: Sources login profile files **Cons**: May not source all interactive RC settings ## Testing To verify fix: ```bash # Test 1: PATH inheritance opencode run "echo \$PATH" | wc -l # Should show many paths, not just system defaults # Test 2: Custom tools available opencode run "which node && which docker && which bun" # Should find tools in custom paths # Test 3: Environment variables opencode run "env | grep -E '(NVM_DIR|CONDA_|HOMEBREW_)'" # Should show custom env vars from RC files ``` ## Additional Context - **Platform**: macOS (Darwin 25.1.0) - **Shell**: zsh (default on macOS) - **OpenCode Version**: Latest dev branch - **Related Code**: `packages/opencode/src/tool/bash.ts` ## References - Node.js spawn documentation: https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options - Shell invocation modes: https://www.gnu.org/software/bash/manual/html_node/Invoking-Bash.html
Author
Owner

@github-actions[bot] commented on GitHub (Dec 1, 2025):

Testing comment creation

@github-actions[bot] commented on GitHub (Dec 1, 2025): Testing comment creation
Author
Owner

@github-actions[bot] commented on GitHub (Dec 1, 2025):

Duplicate Search Complete

I've reviewed the repository's open issues and found NO clear duplicates for this issue.

Related Issue Found:

  • #4961: Feature Request about environment variable security (focuses on secret protection, not PATH inheritance)

This issue appears to be unique in addressing the specific problem of shell sessions spawned by OpenCode not inheriting the full PATH and custom environment variables from the parent shell environment due to non-interactive shell spawning.


Note: Removed test comment. This is the actual duplicate search result.

@github-actions[bot] commented on GitHub (Dec 1, 2025): **Duplicate Search Complete** I've reviewed the repository's open issues and found NO clear duplicates for this issue. **Related Issue Found:** - #4961: Feature Request about environment variable security (focuses on secret protection, not PATH inheritance) This issue appears to be unique in addressing the specific problem of shell sessions spawned by OpenCode not inheriting the full PATH and custom environment variables from the parent shell environment due to non-interactive shell spawning. --- *Note: Removed test comment. This is the actual duplicate search result.*
Author
Owner

@gkzhb commented on GitHub (Jan 10, 2026):

Any progress on this issue? I'm facing the same problem with fish shell on Mac OS.
Many cli tools installed from homebrew cannot be called directly with shell tool.

Image Quite weird there's nix paths but without others like hombrew.

Want to know how this PATH is set and any way to configure this PATH to temporarily fix this before opencode resovles this issue.

Edited:
After some experiments, I noticed that the bash tool actually launches zsh, even though the SHELL env var reports fish (the source code explicitly disables fish).
Under the bash tool, zsh skips the user’s .zshrc(not taking effects for me) but still sources /etc/zshenv, so I can inject the PATH there or other files sourced by it.

@gkzhb commented on GitHub (Jan 10, 2026): Any progress on this issue? I'm facing the same problem with fish shell on Mac OS. Many cli tools installed from homebrew cannot be called directly with shell tool. <img width="2510" height="186" alt="Image" src="https://github.com/user-attachments/assets/da0c6fa1-8f15-44ff-9ee7-7c2c4457123f" /> Quite weird there's nix paths but without others like hombrew. Want to know how this `PATH` is set and any way to configure this PATH to temporarily fix this before opencode resovles this issue. **Edited:** After some experiments, I noticed that the `bash` tool actually launches zsh, even though the `SHELL` env var reports fish (the source code explicitly disables fish). Under the bash tool, zsh skips the user’s `.zshrc`(not taking effects for me) but still sources `/etc/zshenv`, so I can inject the PATH there or other files sourced by it.
Author
Owner

@andrewfenn commented on GitHub (Jan 23, 2026):

I can confirm this bug, hope it gets fixed soon.

@andrewfenn commented on GitHub (Jan 23, 2026): I can confirm this bug, hope it gets fixed soon.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#3246