util: Fix stdin redirect for ShellBuilder - #62078
Merged
Veykril merged 2 commits intoAug 5, 2026
Merged
Conversation
fish's exec builtin requires a command to replace the shell with, so a bare "exec </dev/null" is a usage error. fish responds by printing exec's help text and skipping the rest of the command, and still exits 0, so every agent terminal command under fish silently produced help output instead of running. Go back to wrapping the command in begin/end, which fish accepts. fish exits on a parse error even when interactive, so it never needed the exec form.
The stdin redirect was emitted as "exec </dev/null; <command>", on the same line as the command. POSIX shells parse a whole line before running any of it, so a syntax error in the command discarded the redirect along with it. Interactive dash then recovered from the error and read from the PTY, leaving the agent terminal sitting at a prompt until the user pressed Ctrl+D. Putting the redirect on its own line makes it a separate parse-and-execute unit, so it takes effect before the command line is parsed.
Contributor
Author
Veykril
enabled auto-merge
August 5, 2026 08:05
player1537
pushed a commit
to player1537-forks/zed
that referenced
this pull request
Aug 9, 2026
# Objective This is a follow-up to my own PR zed-industries#59270 which fixed the hang it targeted (zed-industries#55042) only partially and broke `ShellBuilder::build` for fish. **The hang is only half fixed.** The agent's terminal tool wraps commands as `sh -i -c 'exec </dev/null; <command>'`. The redirect sits on the same line as the command, but POSIX shells parse an entire line before executing any of it. So when `<command>` has a syntax error, the whole line is discarded — including the `exec` that was supposed to close stdin. Interactive `dash` then recovers from the error, finds stdin still attached to the PTY, and sits at a `$` prompt until the user presses Ctrl+D. That means zed-industries#59270 only helped when the redirect had already run, i.e. multi-line commands whose error is on a later line. A single-line command still hangs, including `cat <(echo hi)`, the example from that PR's own description. Probably prompts or tool definitions changed since that PR landed which now make it more likely again to run into this issue, at least with Anthropic models. **fish runs no command at all.** zed-industries#59270 also applied the `exec </dev/null; …` form to fish, replacing its `begin; …; end </dev/null` wrapper. But fish's `exec` requires a command to replace the shell with, so a bare `exec </dev/null` is a usage error: fish prints `exec`'s help text, skips the rest of the command, and exits 0. ## Solution For POSIX shells, put the redirect on its own line: ```sh sh -i -c 'exec </dev/null <command>' ``` That makes it a separate parse-and-execute unit, so it runs before the command line is parsed. Any syntax error is then reported by a shell that already has `/dev/null` on stdin, and it exits instead of prompting. For fish, go back to `begin; <command>; end </dev/null`. fish exits on a parse error even when interactive, so it never needed the `exec` form to begin with. Only the `Posix` and `Fish` arms change; the other shells' redirects are untouched. Note this is still a targeted fix for `dash`. The underlying reason a syntax error becomes an interactive prompt is that agent terminals are built with `-i` at all — non-interactive `dash` exits cleanly even with no redirect. Dropping `-i` would remove the whole class of hang, but it would also stop sourcing users' interactive shell config in agent terminals. ## Testing `cargo test -p util --lib shell_builder` covers the changed command assembly. Reviewers can check the shell behavior directly on a PTY, without building Zed: ```sh # hangs at a `$` prompt until Ctrl+D script -qec "sh -i -c 'exec </dev/null; echo hi ;;'" /dev/null # exits immediately with just the syntax error script -qec "sh -i -c 'exec </dev/null echo hi ;;'" /dev/null ``` ```sh fish -i -c 'exec </dev/null; echo test' # prints exec's help, no "test", exit 0 fish -i -c 'begin; echo test; end </dev/null' # prints "test" fish -i -c 'if true' # exit 127, no hang ``` macOS is unaffected in practice because `/bin/sh` there is bash, which exits on a syntax error even with `-i`; Windows goes through the `Cmd`/`PowerShell` arms, which this doesn't touch. A `zsh`-as-`/bin/sh` setup is likewise unaffected. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed agent terminal commands hanging at a shell prompt after a syntax error
audivir
pushed a commit
to audivir/zed
that referenced
this pull request
Aug 10, 2026
# Objective This is a follow-up to my own PR zed-industries#59270 which fixed the hang it targeted (zed-industries#55042) only partially and broke `ShellBuilder::build` for fish. **The hang is only half fixed.** The agent's terminal tool wraps commands as `sh -i -c 'exec </dev/null; <command>'`. The redirect sits on the same line as the command, but POSIX shells parse an entire line before executing any of it. So when `<command>` has a syntax error, the whole line is discarded — including the `exec` that was supposed to close stdin. Interactive `dash` then recovers from the error, finds stdin still attached to the PTY, and sits at a `$` prompt until the user presses Ctrl+D. That means zed-industries#59270 only helped when the redirect had already run, i.e. multi-line commands whose error is on a later line. A single-line command still hangs, including `cat <(echo hi)`, the example from that PR's own description. Probably prompts or tool definitions changed since that PR landed which now make it more likely again to run into this issue, at least with Anthropic models. **fish runs no command at all.** zed-industries#59270 also applied the `exec </dev/null; …` form to fish, replacing its `begin; …; end </dev/null` wrapper. But fish's `exec` requires a command to replace the shell with, so a bare `exec </dev/null` is a usage error: fish prints `exec`'s help text, skips the rest of the command, and exits 0. ## Solution For POSIX shells, put the redirect on its own line: ```sh sh -i -c 'exec </dev/null <command>' ``` That makes it a separate parse-and-execute unit, so it runs before the command line is parsed. Any syntax error is then reported by a shell that already has `/dev/null` on stdin, and it exits instead of prompting. For fish, go back to `begin; <command>; end </dev/null`. fish exits on a parse error even when interactive, so it never needed the `exec` form to begin with. Only the `Posix` and `Fish` arms change; the other shells' redirects are untouched. Note this is still a targeted fix for `dash`. The underlying reason a syntax error becomes an interactive prompt is that agent terminals are built with `-i` at all — non-interactive `dash` exits cleanly even with no redirect. Dropping `-i` would remove the whole class of hang, but it would also stop sourcing users' interactive shell config in agent terminals. ## Testing `cargo test -p util --lib shell_builder` covers the changed command assembly. Reviewers can check the shell behavior directly on a PTY, without building Zed: ```sh # hangs at a `$` prompt until Ctrl+D script -qec "sh -i -c 'exec </dev/null; echo hi ;;'" /dev/null # exits immediately with just the syntax error script -qec "sh -i -c 'exec </dev/null echo hi ;;'" /dev/null ``` ```sh fish -i -c 'exec </dev/null; echo test' # prints exec's help, no "test", exit 0 fish -i -c 'begin; echo test; end </dev/null' # prints "test" fish -i -c 'if true' # exit 127, no hang ``` macOS is unaffected in practice because `/bin/sh` there is bash, which exits on a syntax error even with `-i`; Windows goes through the `Cmd`/`PowerShell` arms, which this doesn't touch. A `zsh`-as-`/bin/sh` setup is likewise unaffected. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed agent terminal commands hanging at a shell prompt after a syntax error
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objective
This is a follow-up to my own PR #59270 which fixed the hang it targeted (#55042) only partially and broke
ShellBuilder::buildfor fish.The hang is only half fixed. The agent's terminal tool wraps commands as
sh -i -c 'exec </dev/null; <command>'. The redirect sits on the same line as the command, but POSIX shells parse an entire line before executing any of it. So when<command>has a syntax error, the whole line is discarded — including theexecthat was supposed to close stdin. Interactivedashthen recovers from the error, finds stdin still attached to the PTY, and sits at a$prompt until the user presses Ctrl+D.That means #59270 only helped when the redirect had already run, i.e. multi-line commands whose error is on a later line. A single-line command still hangs, including
cat <(echo hi), the example from that PR's own description. Probably prompts or tool definitions changed since that PR landed which now make it more likely again to run into this issue, at least with Anthropic models.fish runs no command at all. #59270 also applied the
exec </dev/null; …form to fish, replacing itsbegin; …; end </dev/nullwrapper. But fish'sexecrequires a command to replace the shell with, so a bareexec </dev/nullis a usage error: fish printsexec's help text, skips the rest of the command, and exits 0.Solution
For POSIX shells, put the redirect on its own line:
That makes it a separate parse-and-execute unit, so it runs before the command line is parsed. Any syntax error is then reported by a shell that already has
/dev/nullon stdin, and it exits instead of prompting.For fish, go back to
begin; <command>; end </dev/null. fish exits on a parse error even when interactive, so it never needed theexecform to begin with.Only the
PosixandFisharms change; the other shells' redirects are untouched.Note this is still a targeted fix for
dash. The underlying reason a syntax error becomes an interactive prompt is that agent terminals are built with-iat all — non-interactivedashexits cleanly even with no redirect. Dropping-iwould remove the whole class of hang, but it would also stop sourcing users' interactive shell config in agent terminals.Testing
cargo test -p util --lib shell_buildercovers the changed command assembly.Reviewers can check the shell behavior directly on a PTY, without building Zed:
macOS is unaffected in practice because
/bin/shthere is bash, which exits on a syntax error even with-i; Windows goes through theCmd/PowerShellarms, which this doesn't touch. Azsh-as-/bin/shsetup is likewise unaffected.Self-Review Checklist:
Release Notes: