🚀 Release Notes
This is a significant release, containing several months of work and more than 200 merged PRs. It includes a broad set of new features, bug fixes, and quality improvements, most notably a myriad of bash compatibility fixes that materially raise the bar on which real-world scripts and interactive sessions brush can handle.
Some key highlights:
- A meaningful step forward in bash compatibility. Major bash language features are now implemented or substantially expanded, e.g.:
set -e,set -u,pipefail,failglob, theERRtrap, coprocesses, and a great deal more. - Improved robustness across edge cases. Closed pipes, broken stdout, unusual file-descriptor states, non-UTF8 history files, and platform corner cases are now handled gracefully. A systematic audit also removed an entire class of avoidable failure modes.
- Broader platform support. Using brush as a login shell on macOS is now supported, Windows path handling is overhauled, FreeBSD, Android and 32-bit targets build cleanly again, and
wasm32-wasip2is now exercised in CI. - A more capable interactive shell. Optional TOML config, zsh-style
preexec/precmdhooks, experimental terminal integration, expanded readline-macro support, and many completion improvements. - API improvements and foundations for what's next. Scaffolding for a
winnow-based parser, a genericShell<Extensions>for embedders, an opt-in bundled-coreutils build, andserdefeatures for both AST and shell state. This work has required breaking changes to API surface, mostly tobrush-core. Some changes were also made tobrush-parser's API but we expect migration for parser-only consumers to be straightforward and relatively minimal. Please reach out to us if you encounter challenges in this area.
A heartfelt thank-you to everyone who filed issues, reproduced bugs on unusual platforms, and contributed code over the last several months 🙏. A significant percentage of the work below is in direct response to community reports.
✅ bash compatibility
This release expands brush's coverage of widely-used bash language features. Among the most commonly-relied-on additions:
set -e(errexit) andpipefail(#852) are implemented with bash-faithful semantics, including the long list of exemptions bash has accumulated over the years. See the compatibility reference for the current status of supportedsetoptions.set -u(nounset) (#774, #1007) now actually errors on unset variable references, including the subtle interaction with${#arr[i]}.failglob(#1011) treats no-match globs as an error.- The
ERRtrap (#1020) is implemented, complementing theEXITtrap work from the previous release. As a corollary,EXITtraps now also fire correctly under-c(#1080). - Coprocesses (
coproc) (#1029, #1068), both parsing and execution. See the compatibility reference. - A new
--noglob/-fcommand-line flag (#1091), proper--option-terminator handling for-c(#1076), and an exit code of2for unknown command-line options (#1050).
Heads up: because
set -e,set -u,pipefail, andfailglobpreviously had limited effect in brush, scripts that ran cleanly under v0.3.0 may now exit with errors that bash would also have produced. This is the intended behavior. If you see a divergence from bash, please file an issue.
Beyond the headline options, this release lands a long list of more focused compatibility fixes. Many are small, but in aggregate they account for the bulk of the change list. Highlights worth calling out:
- Arithmetic got serious attention: high-radix literals (#1019), faithful overflow/underflow (#1017), legacy
$[expr]syntax (#1004), the missing|=and^=assignment operators (#911), array indices inside array indices (#910), arithmetic-forparsing/eval fixes (#1018), and infinite-recursion detection (#1021). - Heredocs are substantially more robust inside command substitutions and quoted contexts (#1014, #1055, #1056, #1067).
- Pattern and extglob correctness work covers escaped parens (#960), backslash preservation (#963), pattern character-set escaping (#824), brace-sequence step direction (#1025), and the
nullglob+ quoted-empty-string interaction (#1035). - Builtins matured across the board:
callerwas added (#812),readis now much closer to feature-complete (#914),mapfile -Oworks (#558),getoptsgotOPTERRsemantics (#1048) and short-option fixes (#827),compgen -Apicked upbinding,command, and reserved-word completion (#814, #1027, #1047),printf %qquotes empty strings as''(#1026), and the rules for shadowing special builtins with functions are now correct (#922). - Variables and prompts:
BASH_ARGC/BASH_ARGV(#1013) and$_(#1030) are populated;OSTYPEis set on macOS (#1097); prompts handle\l,\[/\], and$?correctly (#913, #909, #798); and$-correctly reflects-c(#767).
If you've previously hit a bash-compatibility issue with brush, there's a decent chance it's resolved here. If not, please open an issue!
Robustness improvements
v0.4.0 also includes a substantial amount of plumbing work that won't show up in our feature lists but contributed to raising our quality. A big thanks to contributors who put these (and others) together:
- A systematic
unwrap()audit (#921) hardened brush against a class of unusual-input edge cases. - Closed stdout and stderr are now handled gracefully; this is important when brush is on the producing side of a
| headpipeline (#873, #875). - File-descriptor failures (e.g.,
dupfailures from clone) are now reported cleanly rather than aborting (#1051). - Path search now finds executables reachable via symlinks (#991, #992), broadening the set of environments where commands resolve as expected.
- History import tolerates non-UTF8 and otherwise unusual entries (#878).
- Regex-cache contention under concurrent use is reduced (#1043), with additional allocation-reduction work in brace expansion and other hot paths (#884, #886, #936, #940).
Broader platform support
We aim for portability and this release advances that goal on several fronts:
- macOS: using brush as your login shell is now supported, thanks to a fix for a startup hang in that scenario (#1095). If you'd like to set brush as your
chshtarget on a Mac, please give it a try. - Windows: broad path-handling improvements (#1075), proper
/dev/nullemulation (#1044, #1104), and CI now exercises the Windows test suite (#1083) to keep the platform on solid footing. Windows support remains in an experimental state, but it has matured meaningfully this cycle. If you've tried brush on Windows previously, this is a good time to revisit, and we'd appreciate hearing how it goes. - FreeBSD builds again (#980).
- Android and 32-bit targets build cleanly (#1070).
- WebAssembly:
split_pathsis now safe under WASM (#1064), and basicwasm32-wasip2smoke tests run in CI (#1098).
Interactive shell improvements
The interactive experience picked up several things that interactive-shell users have requested:
- TOML configuration file for
brush-shell: an opt-in alternative to shell-script-based configuration for brush-specific shell-level settings (#895). See the configuration reference for the file format, location, and available settings. Experimental. preexec/precmdhooks in the style of zsh: useful for prompt frameworks, timing, integration tools (#652). Enable via the[experimental] zsh-hookssetting documented in the configuration reference. Experimental.- Shell/terminal integration: semantic prompt and command marking that modern terminals can use to enable command navigation, exit-status display, and similar features (#872). Enable via the
[experimental] terminal-shell-integrationsetting in the configuration reference. Experimental. - Readline macros: initial support (#880) plus follow-up to handle more macro forms found in real
inputrcfiles (#967). Many additional fixes to improve compatibility with shell extension tools likestarship,atuin,fzf, and others. - Hint-acceptance key bindings are now exposed (#802).
- A bag of completion fixes that mostly fall under "things you'd never notice unless they were broken": filenames with spaces and special characters now escape correctly (#870); shell variables get a sensible default fallback completion (#954);
.and..are offered (#887);mark-directoriesis honored (#817);COMP_KEY/COMP_TYPEare populated for completion functions (#1008);COMP_WORDBREAKSdefault matches bash (#828); word tokenizing during completion was corrected (#816); and deduplication of completions is deferred to the presentation layer so functions see the full list (#1012). - Profile/rc loading is now deferred until the input backend is attached (#879), which matters for rc files that probe terminal capabilities at startup.
For embedders and crate consumers
The brush crates are published to crates.io, and several of the changes in this release are aimed at people building on top of brush-core, brush-parser, et al. The most important ones:
Shellis now generic over aShellExtensionstype parameter (#941). This is the scaffolding that lets embedders extend brush with custom builtins, custom variable behavior, and other hooks without forking. Existing code that namesShelldirectly will need to either pick a concrete extension type or thread the generic through; a default extension type is provided for the common case. We plan to continue building on this for better low-overhead, static inspection and extension ofbrush-corebehaviors.Shellfields are now private (#900) andshell.rswas split intoshell/*.rs(#945). Callers using the public re-exports and accessor/builder methods are unaffected; callers reaching into private state will need to migrate.serdefeatures are now available on bothbrush-parser(#783) andbrush-core(#831) for AST and shell-state serialization. This makes a number of new use cases viable, e.g.: memoizing initialization, snapshotting shell state.- PEG parser reorganization (#899) along with scaffolding for a future
winnow-based parser (#974). The PEG parser remains the production parser; the winnow work is groundwork for a faster, more diagnosable replacement in a future release. brush-parser:ParsingNearTokenwas renamed toParsingNear(#1022).- MSRV bumped to Rust 1.88 (#981).
fuzz-testingCargo feature was renamed toarbitrary(#844) to better reflect what it actually gates.- Build/test/validate scripting moved to
cargo xtasksubcommands (#898); seecargo xtask --help.
Experimental work to keep an eye on
A few items in this release are intentionally marked experimental; they are usable today, but the shape of the API or UX may evolve based on feedback (hint: we want your feedback!):
- Bundled coreutils builtins behind a feature flag (#1031). Opt in at build time to ship a single binary that resolves common utilities (
ls,cat, etc.) internally, useful for container/embedded scenarios where a coreutils package isn't available, or for platforms like Windows that don't typically offer these utilities. See the experimental features reference. - Full shell-state serialization via the experimental
savebuiltin (#835): primarily a debugging and tooling aid today, but a building block for future features. See the experimental features reference. - The TOML config,
preexec/precmd, and terminal-integration features mentioned above are all in the experimental bucket. The experimental features reference covers how to enable each one. Try them and tell us what feels off.
Application-level breaking changes
If you're a script author or interactive user (rather than an embedder), these are the changes most likely to be visible to you:
- Stricter
set -e/set -u/pipefail/failglobenforcement. These options had limited effect in v0.3.0; they now behave like bash. Scripts that previously ran without surfacing errors may need updates. readonlynow operates on global scope (#1003), matching bash. Code that depended on the prior local behavior will observe a difference.- Reserved words are rejected as function names (#978).
- Unknown command-line options exit with code
2(#1050), matching bash. - Profile/rc loading happens after input backend attach (#879). Visible only to rc files that probed for terminal capabilities at startup.
🙏 Acknowledgments
Releases like this one don't happen without the people who file thoughtful issues, reproduce bugs on platforms the maintainers don't have access to, and send code. Thank you to everyone who contributed to this release! Whether your name appears below or you helped via an issue, a discussion, or a reproducing test case we value and count on your contribution.
Special thanks go to the contributors whose work shows up in many of the bullet points above:
- @lu-zero - has been the driving force behind a multi-release effort to modernize brush's parser and improve bash compatibility from the ground up. The work landing in this release is laying the groundwork for substantially more significant parser, diagnostics, and compatibility improvements in upcoming release: enriched ASTs, source-location tracking, parser reorganization, and the early scaffolding for a
winnow-based parser. Thank you for the sustained, foundational contributions. - @Elsie19 - focused, high-quality bash-compat fixes spread across builtins, expansion, and corner cases.
- @oech3 - ongoing edge-case testing, exit-code validation, and bug finding.
- @StudioLE - surgical heredoc / tokenizer fixes that resolved a cluster of long-standing parser bugs.
- @takeshiD and @xtqqczze - repeated, well-targeted contributions across the codebase.
And thank you to everyone else who authored or co-authored a commit in this release:
- @cataggar
- @cilki
- @hwittenborn
- @Its-Just-Nans
- @MasahikoSawada
- @puffnfresh
- @purplesyringa
- @theoparis
- @tmfink
- @YumeYuka
- @zvyaan
If you contributed and aren't listed here, that's a bug and unintentional! Please let me know and I'll correct it.
The full change list
For the exhaustive, auto-generated list of every commit and PR since brush v0.3.0, see CHANGELOG.md.