Repository navigation
fix(interpreter): terminate command-resolution diagnostics with a newline - #2443
Merged
Merged
Conversation
…line
`nosuchcmd` followed by `alsomissing` wrote a single run-on line:
bash: nosuchcmd: command not foundbash: alsomissing: command not found
Real bash ends every diagnostic with a newline, so the two failures are two
lines. The command-resolution messages were built with
`format!("bash: {}: command not found", name)` and never terminated, and the
CLI writes stderr through verbatim, so the run-on reached the terminal too --
the last diagnostic did not even end its line before the prompt.
Six emit sites, all on the command-resolution path: `command_not_found_message`
(both the hint and the suggestion return), the two logic-only `command not
found` refusals, and the stat / directory / permission diagnostics in
`try_execute_script_by_path`; plus the process-substitution refusal in
`redirection.rs`, whose sibling return in the same function already terminated.
`Error::Execution` payloads are deliberately left alone: they surface through a
`Display` impl, not through stderr.
This is what left nightly `glob_fuzz` red (run 229,
`crash-bd17afb20a1f27f7c30dc099a77b76e7b26f13bb`, `\n$r\n~\n-Tok:\n~:`). The
harness's shell-echo filter matches one diagnostic per line, which needs each
diagnostic to *end* its line. Three concatenated diagnostics formed one line
that opened with a recognized template but closed on a suggestion suffix, so
nothing was stripped and the echoed `-Tok:` -- rendered `-Tok::` by the
template's own `: ` separator -- tripped the banned parser-token shape `Tok::`.
The input pre-filter could not have caught it: the input holds one colon and
the second comes from the template. Fixed at the source rather than by widening
the filter.
Before / after, `bashkit -c $'nosuchcmd\nalsomissing' 2>&1 | cat -A`:
- bash: nosuchcmd: command not foundbash: alsomissing: command not found
+ bash: nosuchcmd: command not found$
+ bash: alsomissing: command not found$
matching GNU bash 5.2.21.
Coverage: the crash input replays through all three scripts the target builds,
each diagnostic's terminator is pinned, and two consecutive failures are
asserted to land on separate lines.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
bashkit | 2213d5d | Commit Preview URL Branch Preview URL |
Sep 24 2026, 09:38 AM |
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.
What changed
Every
bash:diagnostic on the command-resolution path now ends its own line, theway real bash writes them. Previously they were built unterminated, so consecutive
failures ran together into a single line and the last one did not end its line at all.
This is user-visible, not cosmetic: the CLI writes stderr through verbatim, so the
run-on reached the terminal, and any host that splits stderr into lines counted two
failures as one.
Six emit sites, all on the command-resolution path —
command_not_found_message(both the hint and the suggestion return), the two logic-only
command not foundrefusals, and the stat / directory / permission diagnostics in
try_execute_script_by_path— plus the process-substitution refusal inredirection.rs, whose sibling return in the same function already terminated.Error::Executionpayloads are deliberately left unterminated: they surface througha
Displayimpl, not through stderr.Why
This is what left nightly
glob_fuzzred — the only failing check onmain(run 229,
crash-bd17afb20a1f27f7c30dc099a77b76e7b26f13bb, input\n$r\n~\n-Tok:\n~:).The fuzz harness's shell-echo filter matches one diagnostic per line, which requires
each diagnostic to end its line. Three concatenated diagnostics formed one line that
opened with a recognized template but closed on a suggestion suffix, so nothing was
stripped and the echoed
-Tok:— rendered-Tok::by the template's own:separator — tripped the banned parser-token shape
Tok:::The target's input pre-filter could not have caught this one: the input holds a single
colon and the second comes from the template. Fixed at the source rather than by
widening the filter — runs 218 and 219 already widened it twice.
Before / After
bashkit -c $'nosuchcmd\nalsomissing' 2>&1 | cat -A($marks end of line):Matching GNU bash 5.2.21 on the same input:
The crash input reproduces the CI panic locally before the fix and passes after it.
Risk
unterminated form by equality; the repo's assertions are
contains-style, and theone unit test pinning the exact message was updated. The three host-parity gates
(bash, sed, grep) compare against the real tools and pass — this moves output
closer to bash, which is what those gates measure.
Error::Executionmessages are untouched, so errorDisplayoutput isunchanged.
Validation
just check(fmt, clippy-D warnings, tests, script unit tests, capabilityparity, OKF, doc links, workflow parity) — green
cargo test --workspace --lib --bins --tests --features http_client,ssh,sqlite—3484 + 1838 + others passing
cargo test -p bashkit --test integration --features http_client,ssh -- spec_tests:: --ignored—all three host-tool parity gates pass
realfs,failpoints(--test-threads=1),proptest_security— greenssh_supabase_testsfails in this sandbox (no raw SSHegress); confirmed it fails identically on unmodified
main.Checklist
Generated by Claude Code