Repository navigation
fix(derive): ignore closed pipes instead of panicking in generated parse() - #1484
Conversation
…rse() `Cli::parse()` printed help, version, spec and completion answers with `print!`, and failures and deprecation warnings with `eprint!`. Those panic when the reader has gone away, so `cli --help | head -1` or a completion the shell cancelled crashed the CLI, and aborted with a core dump in `panic = "abort"` builds. Route them through `usage_argv` helpers that ignore a failed write; exit statuses are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughUsage output and generated parser output now use helpers that ignore write and flush failures. An integration test checks selected commands with closed stdout or stderr pipes. ChangesSafe output handling
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to The output changes are ready for normal checks before merging; no actionable risk remains identified. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Instruction counts
No instruction-count regression above 1%. Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run. Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes. Shadow comparisonParsing
|
<!-- entire-trail-link-start --> https://entire.io/gh/jdx/usage/trails/33 <!-- entire-trail-link-end --> ### 🚀 Features - **(cli)** publish a usage agent skill with packslip by [@jdx](https://github.com/jdx) in [#1509](#1509) - **(complete)** complete a wrapped command's arguments with its own shell completion via delegate= by [@jdx](https://github.com/jdx) in [#1498](#1498) - **(lib)** add getters that read FlagMeta, CommandMeta and ArgMeta fields wherever they live by [@jdx](https://github.com/jdx) in [#1491](#1491) - **(lib)** export the chosen subcommand to scripts as usage_cmd by [@jdx](https://github.com/jdx) in [#1505](#1505) - **(spec)** let a complete node sit inside the arg it completes by [@jdx](https://github.com/jdx) in [#1496](#1496) - **(spec)** list an arg's possible values from a command with `choices run=` by [@jdx](https://github.com/jdx) in [#1497](#1497) ### 🐛 Bug Fixes - **(bash)** complete `--flag=` values when the cursor sits after `=` by [@jdx](https://github.com/jdx) in [#1499](#1499) - **(cli)** run scripts passed to usage bash through a pipe or process substitution by [@jdx](https://github.com/jdx) in [#1494](#1494) - **(cli)** keep usage explain from running a spec's choices run= commands by [@jdx](https://github.com/jdx) in [#1503](#1503) - **(complete)** read --line=LINE in completion requests from typed completers by [@jdx](https://github.com/jdx) in [#1487](#1487) - **(complete)** show completion parse errors without garbling the prompt by [@jdx](https://github.com/jdx) in [#1495](#1495) - **(derive)** ignore closed pipes instead of panicking in generated parse() by [@jdx](https://github.com/jdx) in [#1484](#1484) - **(derive)** keep generated async dispatch from reserving stack for every command by [@jdx](https://github.com/jdx) in [#1488](#1488) - **(fish)** complete words the user has started quoting by [@jdx](https://github.com/jdx) in [#1500](#1500) - **(lib)** resolve inherited usage aliases after multiline workspace entries by [@jdx](https://github.com/jdx) in [#1507](#1507) - **(lib)** make published usage-rs tests self-contained by [@jdx](https://github.com/jdx) in [#1510](#1510) - **(zsh)** stop the completion-init handler from breaking file completion for other commands by [@jdx](https://github.com/jdx) in [#1493](#1493) ### 📚 Documentation - **(cli)** describe fig output as the legacy Fig format by [@jdx](https://github.com/jdx) in [2a1bef6](2a1bef6) - clarify CLI frameworks and generators on the homepage by [@jdx](https://github.com/jdx) in [#1482](#1482) ### 🛡️ Security - remove Entire trail runners by [@jdx](https://github.com/jdx) in [#1485](#1485) ### 🔍 Other Changes - float jdx tools and aube on latest without a release-age delay by [@jdx](https://github.com/jdx) in [#1486](#1486) - run cargo-semver-checks on every published crate by [@jdx](https://github.com/jdx) in [#1490](#1490) - fail pull requests that grow the usage CLI or a derived CLI by more than 1% by [@jdx](https://github.com/jdx) in [#1492](#1492) - make the binary-size check measure the pull request's own code by [@jdx](https://github.com/jdx) in [#1501](#1501) ### 📦️ Dependency Updates - bump jdx/renovate-config workflows to c736149 by [@jdx](https://github.com/jdx) in [06f2f1b](06f2f1b) - bump jdx/renovate-config workflows to aa49efc by [@jdx](https://github.com/jdx) in [9aaedef](9aaedef) - bump jdx/renovate-config workflows to 5b46432 by [@jdx](https://github.com/jdx) in [36f8681](36f8681) - pin jdx/renovate-config workflows to v1.0.0 by [@jdx](https://github.com/jdx) in [a31ae2f](a31ae2f) - update communique to 1.4.2 in mise.lock by [@jdx](https://github.com/jdx) in [805396f](805396f) - upgrade locked mise tools by [@jdx](https://github.com/jdx) in [d8b761d](d8b761d) - update jdx/packslip action to v1.4.0 by [@jdx](https://github.com/jdx) in [#1508](#1508)
https://entire.io/gh/jdx/usage/trails/34
A CLI built with
#[derive(usage_rs::Cli)]no longer crashes when the process reading its output goes away. Before this fix, the generatedCli::parse()printed withprint!/eprint!, which panic on a failed write. Somycli --help | head -1, a pager quit early, or a completion that an as-you-type shell driver cancelled crashed the CLI. In apanic = "abort"build it aborted with SIGABRT and a core dump.Before (a derive CLI, reader closed):
After: it exits with the status it would have had anyway: 0 for help, version, spec and completion answers, and 2 for a failure rendered to a closed stderr.
This affects every output path in the generated entry point:
--version(__usage_exit_on_error,__usage_exit_version)__usage_spec__endpoint and the completion protocolAll of them now go through two hidden
usage_argvhelpers,__usage_printand__usage_eprint, which ignore the write error. Each caller either exits right away or has already finished a successful parse, so nobody is left to report the error to. Exit statuses don't change.Found through jdx/hk#1441, where hk's cancelled completions aborted inside the generated
parse(). hk is working around it with a panic hook in jdx/hk#1442. mise isn't affected because it doesn't callparse(); it was fixed separately in jdx/mise#13527.Testing
New
a_closed_reader_is_not_a_crashinusage-rs/tests/external.rsruns theruntime-identityfixture with its stdout attached to a pipe whose read end is dropped before the child starts, so the write always fails and nothing is left to timing. It covers--help,--versionand__usage_spec__on a closed stdout, and a parse failure on a closed stderr.main(exit 101 on--help) and passes with this change.cargo test --workspaceandcargo clippy --workspace --all-features --all-targetspass.mise run renderandmise run gen-shadowproduce no diff.AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5-5; version: 2.1.270.
🤖 Generated with Claude Code
Note
Low Risk
Behavior change is limited to error handling on I/O write failure at process exit; exit statuses are unchanged and successful writes are unaffected.
Overview
Generated
Cli::parse()and related entry points no longer panic on broken pipes when the reader closes early (--help | head, cancelled shell completions, etc.). Previouslyprint!/eprint!turned a failed write into a crash (worse underpanic = "abort").The runtime adds hidden
__usage_printand__usage_eprinthelpers that write to stdout/stderr and ignore write errors, preserving the exit codes callers already chose (0 for help/version/spec/completion, 2 for failures).__usage_exit_on_error,__usage_exit_version, and the derive codegen for deprecation warnings,__usage_spec__, and completion intercepts all route through these helpers instead ofprint!/eprint!.A new integration test runs a fixture with a deliberately closed pipe on stdout or stderr and asserts success/failure exit codes without a panic message.
Reviewed by Cursor Bugbot for commit c81e4f9. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit