brush version
brush 0.4.0 (cargo:0.4.0)
bash version (for comparison)
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin24)
Platform / OS
macOS
Steps to reproduce
Ran terminal command: sw_vers; echo "---"; uname -a; echo "---"; brush --version; echo "---"; brew info brush 2>&1 | head -10; echo "---"; echo $TERM_PROGRAM; echo $TERM
Bug Report: brush hangs indefinitely in any interactive session attached to a real controlling terminal (pty)
Project: brush-shell (brush)
Version: 0.4.0 (brush 0.4.0 (cargo:0.4.0)), installed via Homebrew (homebrew-core, bottled)
Environment
- OS: macOS 15.8 (Build 24H20)
- Kernel: Darwin 24.6.0, arm64 (Apple Silicon, T8103)
- Install path:
brush (Homebrew brush formula)
- Reproduced both as the account's login shell (
dscl UserShell) and by invoking the binary directly
Summary
brush hangs forever (never prints a prompt, never returns) whenever it is started as an interactive shell attached to a real controlling terminal (pty) — regardless of whether it's invoked as a login shell (-l), a plain interactive shell (-i), or with no flags at all. It only works correctly when stdin is a pipe (non-tty), which masked the bug during quick manual testing (echo cmd | brush works fine).
This makes brush completely unusable as an interactive/login shell today: setting it as the macOS login shell (chsh -s /opt/homebrew/bin/brush) causes every new Terminal window/tab to hang on open.
Reproduction
Using macOS script to allocate a real pty (a plain pipe does not reproduce the bug):
# Hangs (killed by timeout, exit 124):
script -q /dev/null bash -c 'timeout 8 brush -l' # login shell
script -q /dev/null bash -c 'timeout 8 brush -i' # interactive shell
script -q /dev/null bash -c 'timeout 8 brush' # plain invocation
# Works fine (no real tty attached):
( echo 'echo hi'; sleep 1 ) | timeout 5 brush -i # prints "hi", exits 0
( echo 'echo hi'; sleep 1 ) | timeout 5 brush -l # prints "hi", exits 0
brush -c 'echo hello' # prints "hello", exits 0
All three real-pty invocations above ran for the full 8-second timeout with zero output (no prompt, no echoed input) before being killed.
Isolation — not caused by rc files / dotfiles
To rule out shell startup files, I reran the login-shell case with a completely empty $HOME (no .bash_profile, .bashrc, .profile, or any other dotfile):
mkdir -p /tmp/emptyhome
script -q /dev/null bash -c 'HOME=/tmp/emptyhome timeout 8 brush -l'
# still hangs — exit 124
The hang still occurs, so it is not related to parsing any user rc/profile content — it happens during core interactive/login startup (likely job-control / terminal ownership setup, e.g. tcsetpgrp/process-group handling) before any rc file would even be read.
Impact
- Cannot use
brush as an interactive or login shell in any real terminal (Terminal.app, iTerm2, etc.) — this is likely brush's primary intended use case.
- Particularly dangerous because a user can set it as their macOS login shell (
chsh -s) and then be locked into a hanging shell on every new terminal window, with no rc-file explanation to fall back on for debugging.
Suggested next steps for investigation
- Check job-control initialization on shell startup (
tcsetpgrp/setpgid/signal handling for SIGTTIN/SIGTTOU) when a controlling terminal is present — the tty-vs-pipe behavior split strongly points here.
- Compare behavior across platforms (Linux vs macOS) and terminal emulators to see if this is macOS/pty-specific.
Actual behavior (brush)
The process hangs indefinitely with no output, no prompt, and does not respond to normal input; it only terminates when killed (e.g. via timeout).
Expected behavior
brush -l / brush -i / brush should print an interactive prompt and accept input when attached to a real controlling terminal, just as it does when stdin is piped.
Additional context
No response
brush version
brush 0.4.0 (cargo:0.4.0)
bash version (for comparison)
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin24)
Platform / OS
macOS
Steps to reproduce
Ran terminal command: sw_vers; echo "---"; uname -a; echo "---"; brush --version; echo "---"; brew info brush 2>&1 | head -10; echo "---"; echo $TERM_PROGRAM; echo $TERM
Bug Report:
brushhangs indefinitely in any interactive session attached to a real controlling terminal (pty)Project: brush-shell (
brush)Version: 0.4.0 (
brush 0.4.0 (cargo:0.4.0)), installed via Homebrew (homebrew-core, bottled)Environment
brush(Homebrewbrushformula)dsclUserShell) and by invoking the binary directlySummary
brushhangs forever (never prints a prompt, never returns) whenever it is started as an interactive shell attached to a real controlling terminal (pty) — regardless of whether it's invoked as a login shell (-l), a plain interactive shell (-i), or with no flags at all. It only works correctly when stdin is a pipe (non-tty), which masked the bug during quick manual testing (echo cmd | brushworks fine).This makes
brushcompletely unusable as an interactive/login shell today: setting it as the macOS login shell (chsh -s /opt/homebrew/bin/brush) causes every new Terminal window/tab to hang on open.Reproduction
Using macOS
scriptto allocate a real pty (a plain pipe does not reproduce the bug):All three real-pty invocations above ran for the full 8-second
timeoutwith zero output (no prompt, no echoed input) before being killed.Isolation — not caused by rc files / dotfiles
To rule out shell startup files, I reran the login-shell case with a completely empty
$HOME(no.bash_profile,.bashrc,.profile, or any other dotfile):The hang still occurs, so it is not related to parsing any user rc/profile content — it happens during core interactive/login startup (likely job-control / terminal ownership setup, e.g.
tcsetpgrp/process-group handling) before any rc file would even be read.Impact
brushas an interactive or login shell in any real terminal (Terminal.app, iTerm2, etc.) — this is likely brush's primary intended use case.chsh -s) and then be locked into a hanging shell on every new terminal window, with no rc-file explanation to fall back on for debugging.Suggested next steps for investigation
tcsetpgrp/setpgid/signal handling forSIGTTIN/SIGTTOU) when a controlling terminal is present — the tty-vs-pipe behavior split strongly points here.Actual behavior (brush)
The process hangs indefinitely with no output, no prompt, and does not respond to normal input; it only terminates when killed (e.g. via
timeout).Expected behavior
brush -l/brush -i/brushshould print an interactive prompt and accept input when attached to a real controlling terminal, just as it does when stdin is piped.Additional context
No response