Skip to content

fix(interpreter): terminate command-resolution diagnostics with a newline - #2443

Merged
chaliy merged 1 commit into
mainfrom
claude/pensive-hypatia-4nad6f
Sep 24, 2026
Merged

chaliy merged 1 commit into
mainfrom
claude/pensive-hypatia-4nad6f

Conversation

@chaliy

@chaliy chaliy commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

What changed

Every bash: diagnostic on the command-resolution path now ends its own line, the
way 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 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 unterminated: they surface through
a Display impl, not through stderr.

Why

This is what left nightly glob_fuzz red — the only failing check on main
(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:::

[glob_fuzz] stderr leaks banned shape `Tok::` (after stripping shell echoes):
---raw stderr---
bash: /home/sandbox: Is a directorybash: -Tok:: command not foundbash: ~:: command not found. Did you mean: :?
---stripped---
bash: /home/sandbox: Is a directorybash: -Tok:: command not foundbash: ~:: command not found. Did you mean: :?

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):

- 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 on the same input:

$ bash -c $'~\n-Tok:\nnotacmd' 2>&1 | cat -A
bash: line 1: /root: Is a directory$
bash: line 2: -Tok:: command not found$
bash: line 3: notacmd: command not found$

The crash input reproduces the CI panic locally before the fix and passes after it.

Risk

  • Low
  • Stderr for these diagnostics gains a trailing newline. No test asserted the
    unterminated form by equality; the repo's assertions are contains-style, and the
    one 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.
  • The three Error::Execution messages are untouched, so error Display output is
    unchanged.

Validation

  • just check (fmt, clippy -D warnings, tests, script unit tests, capability
    parity, 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
  • doc tests, realfs, failpoints (--test-threads=1), proptest_security — green
  • Pre-existing and unrelated: ssh_supabase_tests fails in this sandbox (no raw SSH
    egress); confirmed it fails identically on unmodified main.

Checklist

  • Tests added or updated
  • Backward compatibility considered

Generated by Claude Code

…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.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@chaliy
chaliy merged commit 2eb2768 into main Sep 24, 2026
44 checks passed
@chaliy
chaliy deleted the claude/pensive-hypatia-4nad6f branch September 24, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant