Skip to content

fix(builtins): keep printf %q bash-faithful across uutils' always_quote - #2917

Merged
chaliy merged 1 commit into
mainfrom
eve/clever-mccarthy-tl94hl
Nov 13, 2026
Merged

chaliy merged 1 commit into
mainfrom
eve/clever-mccarthy-tl94hl

Conversation

@chaliy

@chaliy chaliy commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

What changed

The weekly Coreutils Args Drift job can build again, and printf %q keeps bash's spelling while it does.

format_support::QuotingStyle gains an always_quote setter that returns self unchanged. The shim provides the name upstream's vendored code now calls, and deliberately drops the behaviour it encodes.

Why

Coreutils Args Drift is red on main. It regenerates fine, then dies at Verify build still passes — so the patch is never uploaded and no drift PR opens. The job is weekly, so it stays red and the coreutils surfaces stay frozen until someone reads the log. (run 43399264556, dispatched on current main to confirm the 2026-11-09 failure was not just the one #2895 already fixed — it is a new, different break.)

Upstream rewrote the %q arm of format/spec.rs from locale_aware_shell_escape(value) to QuotingStyle::SHELL_ESCAPE.always_quote(has_brace) plus locale_aware_escape_name, so GNU printf %q wraps a brace-bearing argument in single quotes. The handwritten shim has no such method.

This is not the usual rename-straddle documented in the knowledge file, where the shim mirrors both spellings of one behaviour. Here bash disagrees with the new GNU behaviour outright, so forwarding the semantics would have silently changed a builtin's output — and the drift PR could never have repaired that, because the open-pr job enforces a generated-only diff and handwritten printf.rs is out of its reach.

Returning self is sound because locale_aware_escape_name routes every shell style to the bash-faithful shell_quote and never consults the flags carried on the style. The shim's variants stay unit-only for the same reason: it carries the names the vendored module uses, not uucore's quoting behaviour.

Before / After

Before — cargo build -p bashkit on the regenerated tree:

error[E0599]: no method named `always_quote` found for enum `format_support::QuotingStyle` in the current scope
   --> crates/bashkit/src/builtins/generated/format/spec.rs:358:56
    |
358 |                 let style = QuotingStyle::SHELL_ESCAPE.always_quote(has_brace);
    |                                                        ^^^^^^^^^^^^ method not found
error: could not compile `bashkit` (lib) due to 1 previous error

After — same build, with upstream's new %q arm spliced into the pinned spec.rs to stand in for the regeneration:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 04s

Behaviour is unchanged, and matches bash. Reference, bash 5.2.21:

argument bash printf %q bashkit (this PR, simulated upstream arm) GNU/uutils after the change
a{b a\{b a\{b 'a{b'
a}b a\}b a\}b 'a}b'
{} \{\} \{\} '{}'
a{b c a\{b\ c a\{b\ c 'a{b c'

Verified e2e: the pre-fix tree reproduces the CI error verbatim; with the fix plus upstream's arm spliced in, cargo build -p bashkit succeeds and bash_spec_tests + the printf/format_support unit tests pass (19 + 23 tests). The splice was reverted; the pinned generated/ tree is untouched by this PR, so the drift PR can still own it.

Risk

  • Low
  • Surface is one new method on a handwritten shim, returning its receiver. No generated file changes, no call site in the current pin — the method is dead code until the drift PR lands the regenerated spec.rs, which is exactly the point.
  • Residual risk: if bash ever adopts GNU's always-quote behaviour for %q, the no-op becomes wrong. quoted_string_escapes_braces_the_way_bash_does pins the current bash output, so that would surface as a failing differential expectation rather than silently.

Checklist

  • Tests added or updated — always_quote_does_not_change_how_braces_are_escaped (the flag is inert) and quoted_string_escapes_braces_the_way_bash_does (the resulting output is bash's, values taken from bash 5.2.21)
  • Backward compatibility considered — internal shim, no public API; printf %q output is byte-identical to before

Knowledge: knowledge/runtimes/coreutils-args-port.md gains a Shim refuses upstream behaviour bash does not have section next to the existing rename-straddle rule.


Generated by Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

The weekly Coreutils Args Drift job is red on main. Regeneration against
upstream HEAD succeeds; the run then dies at "Verify build still passes"
with one error, so the patch is never uploaded and no drift PR opens:

  error[E0599]: no method named `always_quote` found for enum
                `format_support::QuotingStyle` in the current scope
     --> crates/bashkit/src/builtins/generated/format/spec.rs:358:56
  358 |     let style = QuotingStyle::SHELL_ESCAPE.always_quote(has_brace);

Upstream rewrote the `%q` arm of `format/spec.rs` from
`locale_aware_shell_escape(value)` to
`QuotingStyle::SHELL_ESCAPE.always_quote(has_brace)` plus
`locale_aware_escape_name`, so GNU `printf %q` now wraps a brace-bearing
argument in single quotes (`'a{b'`). The handwritten `format_support`
shim has no such method and the drift run cannot get past the build.

This is not the usual rename-straddle: bash disagrees with the new GNU
behaviour. bash 5.2 backslash-escapes braces and never switches to
single quotes — `printf %q 'a{b'` is `a\{b`, `printf %q '{}'` is
`\{\}`. bashkit's printf is bash's, so the shim provides the *name* and
drops the *behaviour*: `QuotingStyle::always_quote` returns `self`
unchanged. That is sound because `locale_aware_escape_name` routes every
shell style to the bash-faithful `shell_quote` and never consults the
flags carried on the style; the shim's variants are unit-only for the
same reason, carrying the names the vendored module uses rather than
uucore's quoting behaviour.

Forwarding the semantics instead would have silently changed a builtin's
output, and the drift PR could not have repaired it: the open-pr job
enforces a generated-only diff, so handwritten `printf.rs` is out of its
reach. Two tests pin the refusal — one that the flag is inert
(`always_quote_does_not_change_how_braces_are_escaped`), one that the
resulting `%q` output is bash's
(`quoted_string_escapes_braces_the_way_bash_does`, values taken from
bash 5.2.21).

Knowledge gains a "Shim refuses upstream behaviour bash does not have"
section next to the existing rename-straddle rule.

Verified: the pre-fix tree reproduces the CI error verbatim, the
post-fix tree builds, and bash_spec_tests plus the printf and
format_support unit tests pass.
@bashkit-pr-reviewer

Copy link
Copy Markdown

Review summary — #2917

Verdict: Sound forward-compat shim; no findings. No review submitted (submit_github_pull_request_review is only for findings).

What the change does: Adds a no-op QuotingStyle::always_quote(bool) -> Self builder plus a pinning test to crates/bashkit/src/builtins/generated/format_support.rs, and records the pending upstream uutils call-site (SHELL_ESCAPE.always_quote(has_brace)) in knowledge/runtimes/coreutils-args-port.md. Head SHA 295e0c5 reviewed; diff was not truncated and the PR snapshot checked out cleanly.

Context read beyond the diff: AGENTS.md (test/knowledge rules), format_support.rs (QuotingStyle enum, locale_aware_escape_name, shell_quote safe-set, shell_escaped_empty, shell_escaped_tilde, escaping assertions), builtins/printf.rs (shell_quote passthrough, diagnostic re-quoting), generated/format/spec.rs (%q → locale_aware_shell_escape, no always_quote call at the pinned rev 6a7d199), and generated/mod.rs (#![allow(dead_code)] covers the currently call-site-less method).

Why no findings:

  • Ignoring the flag is behavior-preserving here: the shim's styles are unit-only names and locale_aware_escape_name routes every shell style to the pre-existing bash-faithful shell_quote regardless of flags, so there is no stored flag to honor. shell_quote backslash-escapes {/} (not in its [A-Za-z0-9_/.:-=+@%] safe set), matching the claimed bash a\{b output.
  • Signature mirrors the upstream call shape (SHELL_ESCAPE.always_quote(has_brace) used as a value), and #[must_use] catches dropped-builder misuse. The only non-test caller after regeneration is the vendored expression, which uses the return value.
  • Knowledge table updated in the same change; the Escape/C_NO_QUOTES dual-variant note and the "spliced into pinned spec.rs" wording accurately describe a test-only splice (production spec.rs at this pin has no always_quote call — confirmed).
  • No sandbox, panic, error-handling, or bash-divergence issues: the touched code is pure string escaping; expect/panic paths are pre-existing #[cfg(test)]-gated invariant assertions.

Not verified: Could not build or run tests (no cargo in this shell); bash-version output claim taken from the PR description and checked for internal consistency with shell_quote only.

@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 295e0c5 Commit Preview URL

Branch Preview URL
Nov 13 2026, 09:30 AM

@chaliy
chaliy merged commit 5efddc4 into main Nov 13, 2026
54 checks passed
@chaliy
chaliy deleted the eve/clever-mccarthy-tl94hl branch November 13, 2026 10:11
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