Skip to content

fix(builtins): read shuf's input range under either generated value type - #2770

Merged
chaliy merged 2 commits into
mainfrom
eve/clever-mccarthy-rkfjf0
Oct 22, 2026
Merged

chaliy merged 2 commits into
mainfrom
eve/clever-mccarthy-rkfjf0

Conversation

@chaliy

@chaliy chaliy commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What changed

shuf now reads its -i LO-HI argument without naming the value type upstream chose for it. read_input_range tries the pre-parsed RangeInclusive<u64> first and falls back to the raw string plus a local parse_input_range; an Err from the typed read means the other surface, not a bad value.

This unblocks the weekly Coreutils Args Drift run, which is the last red workflow on main.

Why

Upstream dropped .value_parser(parse_range) from shuf's input-range arg and moved range parsing into its own uumain. The regenerated surface therefore stores a plain String where the pin stores a RangeInclusive<u64>.

shuf.rs named only the pre-parsed type, so get_one::<RangeInclusive<u64>> mismatched after regeneration. bashkit catches builtin panics, so the only symptom was shuf -i 1-5 printing nothing — 3960/3989 spec cases passed and shuf_input_range failed. Run 39753862402 died there, at Run cat/tac spec tests: after regeneration but before the patch upload, so no drift patch and no PR. That is why the drift has stayed invisible rather than surfacing as a reviewable diff.

This is the documented failure mode from the od case (#2751 / #2754): the drift PR enforces a generated-only diff and cannot repair its own consumers, so the consumer fix lands first and tolerates both surfaces. The new variant is that the two types are not the String/OsString pair helpers::string_value already covers — upstream added and then dropped a value parser yielding a non-string type, moving the arg between "clap pre-parsed it" and "clap handed back raw text". knowledge/runtimes/coreutils-args-port.md records that generalization.

Before / After

Regenerating shuf_args.rs from upstream HEAD (eb7d426) shows the dropped value parser is the only semantic change in shuf's whole surface — every other arg (head-count u64, output path_buf, file-or-args os_string, …) is byte-identical:

-fn parse_range(input_range: &str) -> Result<RangeInclusive<u64>, String> { ... }
                 Arg::new(options::INPUT_RANGE)
-                .value_parser(parse_range)

Before — regenerated surface, CI run 39753862402:

[shuf.test] shuf_input_range
  Expected stdout: "1\n2\n3\n4\n5\n"
  Got stdout:      ""

=== Bash Spec Tests ===
Total: 3989 | Passed: 3960 | Failed: 1 | Skipped: 28

After — same regenerated surface, locally:

$ cargo test -p bashkit --test integration -- spec_tests::bash_spec_tests
test result: ok. 1 passed; 0 failed

$ cargo test -p bashkit --test integration -- shuf
test shuf_resource_tests::shuf_range_head_count_does_not_materialize_full_range ... ok
test shuf_resource_tests::shuf_repeat_head_count_is_checked_before_output_allocation ... ok
test result: ok. 2 passed; 0 failed

Reverting to the pinned surface, all of the above pass again — the point of the change is that both hold at once.

Behaviour note. A malformed -i exits 2 with clap's rendering under the pinned surface, because clap rejects the value before shuf sees it. On the regenerated surface parse_input_range reports it instead and exits 1 with GNU's shuf: invalid input range: '<range>'. The pin bump therefore moves this diagnostic toward GNU, not away from it; no spec case pins the old shape.

Risk

  • Low
  • The typed read is tried first, so the pinned surface takes exactly the path it takes today; the fallback is unreachable until the pin moves.
  • Ordering matters for one subtle reason: the string fallback carries a debug_assert that fires when an arg is present but readable as neither String nor OsString. Reading the typed value first keeps that guard pointed at genuinely unhandled types instead of tripping on the pinned RangeInclusive. absent_input_range_reads_none_under_both_value_types covers the no--i case on both shapes.
  • parse_input_range duplicates upstream's body-code parser, which is not vendored. It can drift independently; the unit tests pin GNU's semantics, including the LO == HI + 1 empty-range spelling.

Checklist

  • Tests added or updated
  • Backward compatibility considered

The weekly Coreutils Args Drift run has been red since upstream dropped
`.value_parser(parse_range)` from `shuf`'s `input-range` arg and moved
range parsing into its own `uumain`. The regenerated surface therefore
stores a plain `String` where the pin stores a `RangeInclusive<u64>`.

`shuf.rs` named only the pre-parsed type, so `get_one::<RangeInclusive<u64>>`
mismatched on the regenerated surface. bashkit catches builtin panics, so
the only symptom was `shuf -i 1-5` printing nothing: 3960/3989 spec cases
passed and `shuf_input_range` failed. Run 39753862402 died there, at *Run
cat/tac spec tests* — after regeneration but before the patch upload, so
no drift patch and no PR, which is why the drift has stayed invisible.

This is the documented failure mode from the `od` case (#2751/#2754): the
drift PR enforces a generated-only diff and cannot repair its own
consumers, so the consumer fix has to land first and tolerate both
surfaces. The new variant is that the two types are not the
`String`/`OsString` pair `helpers::string_value` already covers — upstream
added and then dropped a value parser yielding a non-string type, moving
the arg between "clap pre-parsed it" and "clap handed back raw text".

`read_input_range` tries `try_get_one::<RangeInclusive<u64>>` first and
falls back to `helpers::string_value` plus a local `parse_input_range`; an
`Err` from the typed read means the other surface, not a bad value. The
typed read is tried first so the string fallback never trips the
`debug_assert` guarding a third, unhandled value type.

Verified against both surfaces. Regenerating `shuf_args.rs` from upstream
HEAD (eb7d426) shows the dropped value parser is the only semantic change
in shuf's whole surface. On that regenerated surface the full spec suite,
the shuf unit tests and the shuf resource tests pass; reverting to the
pinned surface, all three pass again. Before the fix, the same
regenerated surface failed `shuf_input_range` with empty stdout.

Behaviour note, recorded in the knowledge: a malformed `-i` exits 2 with
clap's rendering under the pinned surface, because clap rejects the value
before shuf sees it. On the regenerated surface `parse_input_range`
reports it instead and exits 1 with GNU's
`shuf: invalid input range: '<range>'`. The pin bump moves this toward
GNU, not away; no spec case pins the old shape.
The TM-INF-022 static scan rejects `{raw:?}` anywhere under
`src/builtins/`, test asserts included unless marked `// debug-ok`.
Here Debug only added the surrounding quotes, so the message spells
them itself instead of claiming an exemption.
@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 590d35a Commit Preview URL

Branch Preview URL
Oct 22 2026, 09:49 AM

@chaliy
chaliy merged commit 93e9deb into main Oct 22, 2026
50 checks passed
@chaliy
chaliy deleted the eve/clever-mccarthy-rkfjf0 branch October 22, 2026 10:17
chaliy added a commit that referenced this pull request Oct 22, 2026
… modules (#2771)

Regenerated by `bashkit-coreutils-port` against uutils/coreutils@afe06319a
(was d2d96946b). Utilities: cat ls mktemp od readlink realpath shuf stat tac
tee truncate. Vendored modules: format.

First drift PR the workflow has opened since July: the weekly run had been
dying before the patch upload, so the drift never surfaced. #2770 fixed the
`shuf` consumer that was breaking it, and run 39762089314 then went green
end to end, including the Doppler PAT path from #2760 that had never
actually executed.

Review of the diff beyond the revision headers:

- `shuf_args.rs` drops `parse_range` and `.value_parser(parse_range)`, so
  `input-range` now stores a plain `String`. This is the change that broke
  the drift run; `shuf::read_input_range` reads both shapes as of #2770, and
  the spec suite, shuf unit tests and shuf resource tests were verified
  against this exact regenerated surface before that landed. One behaviour
  change rides along, as recorded in the knowledge: a malformed `-i` now
  exits 1 with GNU's `shuf: invalid input range: '<range>'` rather than
  exiting 2 with clap's rendering, because the rejection moves from clap's
  value parser into bashkit. No spec case pinned the old shape.

- `num_parser.rs` gains `SUBNANOSECOND_BINARY_EXPONENT` and rejects
  hexadecimal durations below ~2^-30 seconds. This guard is reachable only
  under `ParseTarget::Duration`, which nothing in bashkit constructs -- the
  sole entry point is `parse`, and its in-crate callers pass `Integral` or
  `Decimal`. Inert for us; vendored verbatim to keep the module aligned to
  one rev.

- Every other file changes only its `Source: uutils/coreutils@<rev>` header.

All 50 checks green.
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