opencode icon indicating copy to clipboard operation
opencode copied to clipboard

Shell sessions losing $PATH value from parent shell

Open Walid-Azur opened this issue 1 month ago • 3 comments

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

  • 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

Walid-Azur avatar Dec 02 '25 02:12 Walid-Azur

Testing comment creation

github-actions[bot] avatar Dec 02 '25 02:12 github-actions[bot]

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] avatar Dec 02 '25 02:12 github-actions[bot]