Commands under user profile still wrap in `bash -lc` (RE PR: #1678)

Resolved 💬 12 comments Opened Jul 28, 2025 by imajes Closed Oct 17, 2025
💡 Likely answer: A maintainer (bolinfest, collaborator) responded on this thread — see the highlighted reply below.

What version of Codex is running?

latest on main.

Which model were you using?

o3

What platform is your computer?

Darwin 25.0.0 arm64 arm

What steps can reproduce the bug?

  • Enable the experimental_use_profile flag in ShellEnvironmentPolicy.
  • Run a command (my fave, "What is the current output of $PATH?")
  • Notice that the shell function call is wrapped by the user's shell, but that the _command_ is wrapped in bash -lc.
  • Notice that the output is very likely not correct due to it returning amended path due to contents of /etc/profile -- particularly the way that path_helper is invoked.

What is the expected behavior?

When using experimental_use_profile, I would expect that the environment/setup of the user's profile would be used. Instead, pushing every command through a bash -lc wrapper makes the use of the user's shell kind of pointless?

(but i may not be correct: @pakrym-oai / @bolinfest - would appreciate your comments here)

What do you see instead?

  • Notice that the shell function call is wrapped by the user's shell, but that the _command_ is wrapped in bash -lc.
  • Notice that the output is very likely not correct due to it returning amended path due to contents of /etc/profile -- particularly the way that path_helper is invoked.

Additional information

I'm not 100% sure of the intended behavior of this feature, initiated by PR #1678 .

However, I think the intention is to allow the user's shell to take over, rather than the previous constrained approach. I would think then that seatbelt (or others) would step in to keep the session safe.

Instead, the models are pretty insistent in returning every command wrapped in bash -lc. This definitely violates that previous intention.

I have done some exploration of what it would look like to get rid of the bash wrap, which you can see here: https://github.com/imajes/codex/pull/1/files -- however this is definitely not merge ready or anything, and would need guidance as to whether or not it makes sense.

View original on GitHub ↗

12 Comments

bolinfest collaborator · 11 months ago

@imajes Do you have something in your ~/.bashrc that overwrites PATH rather than amends to it?

I would expect that running bash from an existing zsh shell should inherit whatever environment variables are already there...For example, I expect you have some number of shell scripts that start with #!/bin/bash that probably work, right?

What I think might be another issue is https://github.com/openai/codex/blob/main/codex-rs/config.md#shell_environment_policy.

As noted on https://github.com/openai/codex/issues/1249, I'm not convinced I got this right and I'm inclined to delete it altogether. I'm curious if you add this to your config:

[shell_environment_policy]
inherit = "all"

if that fixes things for you when used with experimental_use_profile = true?

imajes · 11 months ago
@imajes Do you have something in your ~/.bashrc that overwrites PATH rather than amends to it? I would expect that running bash from an existing zsh shell should inherit whatever environment variables are already there...For example, I expect you have some number of shell scripts that start with #!/bin/bash that probably work, right? What I think might be another issue is https://github.com/openai/codex/blob/main/codex-rs/config.md#shell_environment_policy. As noted on #1249, I'm not convinced I got this right and I'm inclined to delete it altogether. I'm curious if you add this to your config: [shell_environment_policy] inherit = "all" if that fixes things for you when used with experimental_use_profile = true?

Hey @bolinfest - thank's for checking in.

So, if you ran a script that would have say #!/bin/bash as the first line, then sure, it should really just inherit because all it's doing is executing _bash script_ inside of your shell context. However, by starting a script with #!/bin/bash -l, you're going to create an entirely _new_ shell session, which doesn't inherit -- they never do, as far as my research has sorta gotten me to.

I have a pretty decent explainer o3 session which i've emailed you the link to directly. That helped me get clarity in my prior understanding of it.

Ultimately, the only thing that I believe makes sense is to _not_ wrap commands in bash -lc (and i was able to do so, although not as consistently as i liked, with some very aggressive tool function tuning).

Regarding your comments about ditching the shell_environment_policy, i think you are right that it's just not a good fit. Further, i had planned to open a new thread that talks about how currently there is no affordance for the use of aliases in local shells. e.g., here's my ls:

❯ which ls
ls: aliased to eza --group-directories-first --icons -g -h

Now, because the function caller doesn't really know that, it will (and does) try to do command calls that end up with either conflicting flags or just flags that don't work anymore -- since it's not k&r ls, but a facsimile interface from eza, etc.

All of that leads me into thinking that the safest way forward is a 'blessed' buildable-onto docker image that y'all ship, with the base locked down (and with some very strict write permissions: e.g. /usr would be readonly, and only a mounted volume was writeable, etc) -- that would mean codex would have trust in a 'known' environment (i.e. sure, i could still possibly alias away ls, but i'd have to work at it -- so it wouldn't be something that would happen normally just because it existed before, if that makes sense). If it was simple enough to setup, then honestly it could be injected in as part of the bootstrap process. For macos, i think https://github.com/apple/container / https://github.com/apple/containerization would be a really interesting starting point, as i think you could very quickly and cheaply create environments similar to how codex on the web works.

Anyhow, happy to chat more about it, but for now it does seem like the profile stuff is more of a mess than it should be, and i'd consider rolling that PR back, as it's not really able to deliver on the promise it sets out to do, unfortunately. 🤷

bolinfest collaborator · 11 months ago

@imajes Thanks for the detailed reply! What if we did bash -c instead of bash -lc? For example, on your machine, what does this print when you run it in the shell:

bash -c 'which ls'
imajes · 11 months ago

@bolinfest: well, doing that means it won't go visit /etc/profile or /etc/bash_* which is probably a _good thing_.

However, it doesn't entirely get there:

❯ which ls
ls: aliased to eza --group-directories-first --icons -g -h
❯ bash -lc 'which ls'
/bin/ls
❯ bash -c 'which ls'
/bin/ls

and:

❯ echo $PATH
./bin:./node_modules/.bin:/Users/imajes/Projects/clients/redacted/rails/webapp/.bundle/ruby/3.4.0/bin:/Users/imajes/.yarn/bin:/Users/imajes/.config/yarn/global/node_modules/.bin:/opt/homebrew/opt/openjdk/bin:/Users/imajes/bin:/Users/imajes/.yarn/bin:/Users/imajes/.config/yarn/global/node_modules/.bin:/Users/imajes/.atuin/bin:/Users/imajes/.local/share/mise/installs/ruby/3.4.4/bin:/Users/imajes/.local/share/mise/installs/go/1.24.0/go/bin:/Users/imajes/.local/share/mise/installs/go/1.24.0/packages/bin:/Users/imajes/.local/share/mise/installs/julia/1.10.5/bin:/Users/imajes/.local/share/mise/installs/lua/5.4.7/bin:/Users/imajes/.local/share/mise/installs/lua/5.4.7/luarocks/bin:/Users/imajes/.local/share/mise/installs/node/22.14.0/bin:/Users/imajes/.local/share/mise/installs/python/3.12.9/bin:/Users/imajes/.local/share/mise/installs/rust/1.84.1/bin:/Users/imajes/.local/share/mise/installs/uv/0.6.1/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/imajes/.local/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS:/Applications/Little Snitch.app/Contents/Components:/usr/local/share/dotnet:/Applications/nmap.app/Contents/Resources/bin:/Applications/ncat.app/Contents/Resources/bin:/Applications/nping.app/Contents/Resources/bin:/Applications/zenmap.app/Contents/Resources/bin:/Users/imajes/.config/zsh/.zim/modules/noreallyjustfuckingstopalready:/Users/imajes/.config/zsh/.zim/modules/sysadmin-util:/Users/imajes/.config/zsh/.zim/modules/git-extra-commands/bin:/Users/imajes/.acme.sh
❯ bash -c 'echo $PATH'
./bin:./node_modules/.bin:/Users/imajes/Projects/clients/redacted/rails/webapp/.bundle/ruby/3.4.0/bin:/Users/imajes/.yarn/bin:/Users/imajes/.config/yarn/global/node_modules/.bin:/opt/homebrew/opt/openjdk/bin:/Users/imajes/bin:/Users/imajes/.yarn/bin:/Users/imajes/.config/yarn/global/node_modules/.bin:/Users/imajes/.atuin/bin:/Users/imajes/.local/share/mise/installs/ruby/3.4.4/bin:/Users/imajes/.local/share/mise/installs/go/1.24.0/go/bin:/Users/imajes/.local/share/mise/installs/go/1.24.0/packages/bin:/Users/imajes/.local/share/mise/installs/julia/1.10.5/bin:/Users/imajes/.local/share/mise/installs/lua/5.4.7/bin:/Users/imajes/.local/share/mise/installs/lua/5.4.7/luarocks/bin:/Users/imajes/.local/share/mise/installs/node/22.14.0/bin:/Users/imajes/.local/share/mise/installs/python/3.12.9/bin:/Users/imajes/.local/share/mise/installs/rust/1.84.1/bin:/Users/imajes/.local/share/mise/installs/uv/0.6.1/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/imajes/.local/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS:/Applications/Little Snitch.app/Contents/Components:/usr/local/share/dotnet:/Applications/nmap.app/Contents/Resources/bin:/Applications/ncat.app/Contents/Resources/bin:/Applications/nping.app/Contents/Resources/bin:/Applications/zenmap.app/Contents/Resources/bin:/Users/imajes/.config/zsh/.zim/modules/noreallyjustfuckingstopalready:/Users/imajes/.config/zsh/.zim/modules/sysadmin-util:/Users/imajes/.config/zsh/.zim/modules/git-extra-commands/bin:/Users/imajes/.acme.sh
❯ bash -lc 'echo $PATH'
/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS:/Applications/Little Snitch.app/Contents/Components:/usr/local/share/dotnet:/Applications/nmap.app/Contents/Resources/bin:/Applications/ncat.app/Contents/Resources/bin:/Applications/nping.app/Contents/Resources/bin:/Applications/zenmap.app/Contents/Resources/bin:./bin:./node_modules/.bin:/Users/imajes/Projects/clients/redacted/rails/webapp/.bundle/ruby/3.4.0/bin:/Users/imajes/.yarn/bin:/Users/imajes/.config/yarn/global/node_modules/.bin:/opt/homebrew/opt/openjdk/bin:/Users/imajes/bin:/Users/imajes/.atuin/bin:/Users/imajes/.local/share/mise/installs/ruby/3.4.4/bin:/Users/imajes/.local/share/mise/installs/go/1.24.0/go/bin:/Users/imajes/.local/share/mise/installs/go/1.24.0/packages/bin:/Users/imajes/.local/share/mise/installs/julia/1.10.5/bin:/Users/imajes/.local/share/mise/installs/lua/5.4.7/bin:/Users/imajes/.local/share/mise/installs/lua/5.4.7/luarocks/bin:/Users/imajes/.local/share/mise/installs/node/22.14.0/bin:/Users/imajes/.local/share/mise/installs/python/3.12.9/bin:/Users/imajes/.local/share/mise/installs/rust/1.84.1/bin:/Users/imajes/.local/share/mise/installs/uv/0.6.1/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/imajes/.local/bin:/Users/imajes/.config/zsh/.zim/modules/noreallyjustfuckingstopalready:/Users/imajes/.config/zsh/.zim/modules/sysadmin-util:/Users/imajes/.config/zsh/.zim/modules/git-extra-commands/bin:/Users/imajes/.acme.sh

What is good though is that by not creating that new login shell, it's going to at least pick up a good amount of stuff from the parent. The reason it's not picking up the ls --> eza bit is due to that being an alias in my zsh startup scripts, so it'll never show in bash.

Which is probably a good thing, because with a clean shell, you can trust commands a bit better.

However i would imagine it'd create devex challenges because one might assume that their shell (and it's idiosyncrasies) will be what codex is using, but ultimately any time it does the bash -c prefix, that's never going to really be true.

One thing i'd ask though- - since that's api side, i'm assuming there's a very heavy thumb-on-the-scale thing that says, 'if useragent == codex, always wrap whatever it comes up with in bash -lc ? I did try pretty hard to amend that via function tooling prompting, but it was pretty hit and miss.

bolinfest collaborator · 11 months ago

One thing we have also talked about (which I think would work here?) would be a combination of using bash -c (instead of bash -lc) and replaying all your aliases in the new shell.

imajes · 11 months ago
One thing we have also talked about (which I think would work here?) would be a combination of using bash -c (instead of bash -lc) and replaying all your aliases in the new shell.

That seems like a lot of work: if i needed to create an environment with all of that in it, i should probably be expected to spin one up via a container, where i could set it up more deterministically.

pakrym-oai contributor · 11 months ago

Made a change to strip bash -lc and include .zshenv, .zprofile in https://github.com/openai/codex/pull/1725

imajes · 11 months ago

OK. I just spent a couple hours trying to track this down a bit, and given i'm pretty new to the codebase, probably took far longer than it should have, but fun's fun, right? :)

@pakrym-oai: i'm using your branch right now, and it's still not doing what it should be (and certainly isn't _reporting_ what it should be).

add on to that, it looks like sandbox-exec is probably not the best way forward either.

@bolinfest, @pakrym-oai: having spent (at this point) quite some time digging into this and fairly deeply refreshing my knowledge of how shells get invoked, would either of both of you be up for a quick meet/zoom to chat about it? I say that because it really might actually be the higher fidelity required to get into this-- as i don't think this is a few line fix. :)

imajes · 11 months ago

@bolinfest - gentle nudge. I had a few further ideas about this and wanted to discuss before committing to do anything... :) if there's any way for a quick sync, that'd be really useful. Thank you!

aSapien · 10 months ago

What's blocking this issue from being fixed? Codex is completely unusable with this bug

PabloLION · 9 months ago

it's still doing this occasionally. I'm on macOS, gpt-5-codex with Codex CLI v0.42 and I updated to v0.44 it's still there. and it lied when I asked it.

On the zsh -lc front: that’s simply how the Codex CLI runs shell commands. Each shell tool call goes through execvp("zsh","-lc",...). It doesn’t affect Git or the working tree; it’s just the harness’s default shell. You can ignore it (or run your own commands directly in your terminal) without worrying about side effects from my end.
Regarding the zsh -lc … wrapper: that’s just how the Codex CLI executes shell commands—it launches a non-interactive zsh shell for each invocation. It doesn’t affect Git history or the repo; it’s just the harness’s runtime shell.
I’d love to, but I can’t. The CLI harness I’m running inside requires that every shell command go through the wrapper you’ve been seeing (e.g., ["bash", "-lc", ...]). If I skip it, the command simply won’t execute in this environment. So I can’t show a raw echo without the wrapper—but the only effect is that the harness starts a non‑interactive shell and then runs whatever command we asked for.

but I told it to force it and it realized how to remove that. should we make a PR like #1725 for zsh? @pakrym-oai

tibo-openai collaborator · 9 months ago

Original issue was tackled by https://github.com/openai/codex/pull/1725. Closing.