agent: Fix shell hang on shell syntax errors with terminal tool usage - #59270
Merged
Veykril merged 1 commit intoJun 15, 2026
Merged
Conversation
Change stdin redirect from subshell wrapping to exec-level redirect for POSIX and Fish shells. Before: sh -i -c '(command ) </dev/null' After: sh -i -c 'exec </dev/null; command' The subshell approach only redirected stdin for the inner command, leaving the outer shell's stdin connected to the PTY. When a command encountered a syntax error (e.g. process substitution <() in dash), the shell would drop into interactive mode, print a prompt ($), and hang waiting for input. With exec-level redirect, the entire shell process has stdin from /dev/null. After the -c command completes (even on error), the shell reads EOF immediately and exits cleanly. No hang, no prompt. The -i flag is preserved so that shells which support it will still source their interactive init files and enable job control.
Contributor
|
Would this fix #55042? |
Contributor
Author
|
@tredondo I believe so, I didn't know that issue but that's exactly what I tried to fix. Testing welcome |
Veykril
enabled auto-merge
June 15, 2026 06:52
This was referenced Jun 18, 2026
Closed
This was referenced Jul 1, 2026
This was referenced Jul 10, 2026
5 tasks
InfyniteHeap
pushed a commit
to InfyniteHeap/zed
that referenced
this pull request
Aug 5, 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
jolutz
pushed a commit
to jolutz/zed
that referenced
this pull request
Aug 8, 2026
…zed-industries#59270) # Objective When the agent's terminal tool runs a command with an unsupported shell syntax (e.g. process substitution `<()` in dash), the shell prints an error and then drops into interactive mode. It shows a `$` prompt and hangs waiting for input, requiring the user to press Ctrl+D to continue. This happens because the stdin redirect wraps the command in a subshell `(...) </dev/null`, which only closes stdin for the inner command. The outer shell still has its stdin connected to the PTY, so after a syntax error it reads from the PTY and waits for more input. ## Solution Replace the subshell wrapping with an `exec`-level redirect. Instead of: ```sh sh -i -c '(command\n) </dev/null' ``` We now produce: ```sh sh -i -c 'exec </dev/null; command' ``` `exec </dev/null;` closes stdin for the **entire shell process**. If the command fails with a syntax error, the shell immediately gets EOF from stdin and exits cleanly instead of prompting for more input. The `-i` flag is preserved so that shells which support it still source their interactive init files and enable job control. This change applies to `ShellKind::Posix` (sh/bash/dash/zsh) and `ShellKind::Fish` in both `ShellBuilder::build()` and `ShellBuilder::build_no_quote()`. ## Testing - Manually verified that `cat <(echo hi)` (process substitution in dash) no longer hangs: the shell exits immediately with the syntax error ## 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 tool hanging with a `$` prompt when commands encounter syntax errors or unsupported shell features like process substitution
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
When the agent's terminal tool runs a command with an unsupported shell syntax (e.g. process substitution
<()in dash), the shell prints an error and then drops into interactive mode. It shows a$prompt and hangs waiting for input, requiring the user to press Ctrl+D to continue.This happens because the stdin redirect wraps the command in a subshell
(...) </dev/null, which only closes stdin for the inner command. The outer shell still has its stdin connected to the PTY, so after a syntax error it reads from the PTY and waits for more input.Solution
Replace the subshell wrapping with an
exec-level redirect. Instead of:sh -i -c '(command\n) </dev/null'We now produce:
sh -i -c 'exec </dev/null; command'exec </dev/null;closes stdin for the entire shell process. If the command fails with a syntax error, the shell immediately gets EOF from stdin and exits cleanly instead of prompting for more input.The
-iflag is preserved so that shells which support it still source their interactive init files and enable job control.This change applies to
ShellKind::Posix(sh/bash/dash/zsh) andShellKind::Fishin bothShellBuilder::build()andShellBuilder::build_no_quote().Testing
cat <(echo hi)(process substitution in dash) no longer hangs: the shell exits immediately with the syntax errorSelf-Review Checklist:
Release Notes:
$prompt when commands encounter syntax errors or unsupported shell features like process substitution