Skip to content

util: Fix stdin redirect for ShellBuilder - #62078

Merged
Veykril merged 2 commits into
zed-industries:mainfrom
procr1337:fix/stdin-redirect-syntax-error
Aug 5, 2026
Merged

util: Fix stdin redirect for ShellBuilder#62078
Veykril merged 2 commits into
zed-industries:mainfrom
procr1337:fix/stdin-redirect-syntax-error

Conversation

@procr1337

@procr1337 procr1337 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Objective

This is a follow-up to my own PR #59270 which fixed the hang it targeted (#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 #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 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 -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:

# 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
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:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • Fixed agent terminal commands hanging at a shell prompt after a syntax error

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.
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Aug 2, 2026
@procr1337

procr1337 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@Veykril I messed this one up before, here is my attempt at a more complete fix for #55042 and cleanup of a regression in my prior PR

@smitbarmase smitbarmase added the area:ai/agent thread Feedback for Zed's Agent Thread label Aug 3, 2026

@Veykril Veykril left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@Veykril
Veykril enabled auto-merge August 5, 2026 08:05
@Veykril
Veykril added this pull request to the merge queue Aug 5, 2026
Merged via the queue into zed-industries:main with commit 8e18ab0 Aug 5, 2026
53 checks passed
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ai/agent thread Feedback for Zed's Agent Thread cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants