claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

fix(shell): zsh pipes break when nomultios

Open LymanBE opened this issue 7 months ago • 0 comments

Description

Problem: In Claude Code, piped commands like echo "test" | grep test return no output when using zsh with nomultios set.

Root cause: The Bash tool appends < /dev/null to every command to prevent interactive blocking. In zsh with unsetopt multios, this redirection replaces the pipe's stdin entirely, so the consumer command reads only from /dev/null instead of the pipe.

Reproduction

You may set the option and test directly with Claude Code using its bash tool, or directly reproduce the underlying issue via the following:

# zsh with MULTIOS disabled
zsh -c 'unsetopt multios; echo foo | grep foo < /dev/null'   # → (blank)

# zsh default (MULTIOS on)
zsh -c 'setopt multios; echo foo | grep foo < /dev/null'     # → foo

How Claude Code apparently executes commands:

When you run a command through Claude Code's Bash tool, it doesn't execute it directly. Instead, it wraps your command in a shell invocation. The --verbose flag reveals this wrapper:

/bin/zsh -lc 'source [temp-snapshot] && eval "[user-command]" < /dev/null && pwd -P >| [temp-cwd]'

What each part does:

  • /bin/zsh -lc - Runs a login shell with the user's default shell
  • source [temp-snapshot] - Loads shell state/environment snapshot
  • eval "[user-command]" - Executes the user's actual command
  • < /dev/null - The problematic part - redirects stdin to prevent blocking
  • && pwd -P >| [temp-cwd] - Saves working directory for state tracking

The issue: When [user-command] contains a pipe (e.g., echo foo | grep foo), the < /dev/null at the end affects the final command in the pipeline. With nomultios set in zsh, this replaces the pipe input entirely, causing the consumer to read only from /dev/null instead of the pipe.

Note: bash fails the same way if < /dev/null is appended after a pipeline; Claude simply skips the guard for bash, so users don't notice.

Expected

Pipeline output should print regardless of MULTIOS setting or shell type.

Workaround

Enable MULTIOS in your zsh configuration by removing any unsetopt MULTIOS lines from your .zshrc or other startup files. This restores zsh's default behavior where multiple redirections are combined rather than replaced.

Suggested Fix

Move the stdin guard to only affect the first command in a pipeline:

# Instead of: eval "cmd1 | cmd2" < /dev/null
# Use:        eval "< /dev/null cmd1 | cmd2"

This prevents interactive blocking while preserving pipe functionality across all shell configurations.

Environment

  • macOS 15.3.2 (Apple Silicon)
  • zsh 5.9 (setopt | grep multiosnomultios)
  • Claude Code CLI 1.0.30

Related Issues

  • #774 – "Claude code chokes on bash commands with pipes"
  • #1872 – "claude-shell-snapshot-* has a syntax error when using Oh My Zsh"

LymanBE avatar Jun 20 '25 20:06 LymanBE