fix(cli): bound clap frame size by splitting oversized subcommand enums (Refs #215) - #216
Merged
Conversation
…ms (Refs #215) `cargo test -p bashrs --lib` aborted with SIGABRT on pristine main: thread 'cli::tests::test_cli_check_command' has overflowed its stack The test only calls `Cli::parse_from(vec!["rash","check","test.rs"])`. IT IS NOT RECURSION. #215 originally guessed "deep recursion in the bash parser"; that was wrong and is corrected on the issue. gdb shows SIX frames and no cycle: #2 args::Commands::augment_subcommands (27 variants) #1 args_corpus::CorpusCommands::augment_subcommands (73 variants) #0 args_corpus_analysis::CorpusAnalysisCommands::augment_subcommands (63) Three nested clap-derive functions, each ONE ENORMOUS FRAME. Per clap_derive-4.6.4 gen_augment, the generated body is a flat sequence with one NEW binding per variant: let __clap_app = __clap_app.subcommand({ ...variant N... }); At opt-level 0 every binding gets its own stack slot with no coalescing, and clap::Command is large. Measured ~12KB of stack PER VARIANT: deleting the 63-variant Analysis flatten moved the requirement from ~2816KB to ~2048KB. So the thing to bound is VARIANTS PER GENERATED FUNCTION, not depth. clap emits `#[command(flatten)]` children as SEQUENTIAL calls, so peak stack is parent + MAX(child), not parent + SUM(children) — and flatten adds no CLI nesting level. This repo already used that pattern: CorpusAnalysisCommands was itself split out of CorpusCommands this way. CorpusCommands 73 variants -> 5 shell + groups of 23/24/24 CorpusAnalysisCommands 63 variants -> 3 shell + groups of 21/21/21 Measured on this tree with [profile.test] opt-level left at 0: * stack requirement 2816KB -> 1664KB; margin vs the 2MB default thread stack goes from -768KB to +384KB * cli::tests: 13 passed (was SIGABRT) * full suite: 14593 passed, 0 stack overflows opt-level = 1 in [profile.test] also makes the symptom disappear, but it compensates instead of removing the cause and that profile's own comment flags coverage sensitivity. This split holds at any optimization level. CLI SURFACE IS UNCHANGED — the whole point. Verified by diffing the full raw `--help` text of every node against a pristine build of HEAD: 3286 lines across 208 subcommand nodes, BYTE-IDENTICAL. The one remaining suite failure (test_cov_keyring_init_with_import, a tempdir write race) reproduces identically on pristine HEAD and is unrelated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merged
noahgift
added a commit
that referenced
this pull request
Aug 12, 2026
Ships the user-facing fixes accumulated since 6.66.2, none of which
reach anyone until this is published:
- five lint false positives that were driving users to disable
`bashrs lint` (#219, GH-217, GH-209)
- CLI stack overflow from an oversized clap frame (#216, #215)
- RUSTSEC-2026-0204, crossbeam-epoch 0.9.20 (#210)
plus internal repairs: kani harnesses compile under cfg(kani) again
(#221), bashrs-oracle's test module compiles and the workspace is
actually tested (#223), and a workflow template stopped being run as a
workflow (#222).
Cargo.lock regenerated in the same commit -- forjar's 1.12.4 release
tripped its lockfile-preflight by bumping Cargo.toml alone.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #215.
It is not recursion
The issue originally guessed "deep recursion in the bash parser." Wrong — corrected on the issue. gdb shows six frames, no cycle:
The failing test only calls
Cli::parse_from(vec!["rash","check","test.rs"]).Why the frames are huge
Per
clap_derive-4.6.4gen_augment, the body is a flat sequence with one new binding per variant:At opt-level 0 each binding gets its own uncoalesced stack slot and
clap::Commandis large. Measured ~12KB/variant: deleting the 63-variant flatten moved the requirement 2816KB → 2048KB.The fix
Bound variants per generated function. clap emits
#[command(flatten)]children as sequential calls, so peak = parent + max(child), not parent + sum — and flatten adds no CLI nesting. The repo already used this pattern (CorpusAnalysisCommandswas split out ofCorpusCommandsthe same way).CorpusCommandsCorpusAnalysisCommandsResults (with
[profile.test] opt-levelleft at 0)cli::testsopt-level = 1also hides the symptom, but it compensates instead of removing the cause, and that profile's own comment flags coverage sensitivity. This split holds at any optimization level.CLI surface is unchanged — the whole point
Diffed the full raw
--helptext of every node against a pristine build of HEAD:3286 lines across 208 subcommand nodes — byte-identical.
Known unrelated failure
test_cov_keyring_init_with_import(a tempdir write race) fails in the full suite — and reproduces identically on pristine HEAD, verified by differential run. Not from this change.Once this ships, the
RUST_MIN_STACK=16777216workaround in paiml/infra#170's clean-room gate can be removed.🤖 Generated with Claude Code