Skip to content

Reject --flag=value on no-argument boolean flags#540

Draft
julesmcrt wants to merge 2 commits into
jules.macret/rm-builtinfrom
jules.macret/noarg-bool-flags
Draft

Reject --flag=value on no-argument boolean flags#540
julesmcrt wants to merge 2 commits into
jules.macret/rm-builtinfrom
jules.macret/noarg-bool-flags

Conversation

@julesmcrt

@julesmcrt julesmcrt commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

pflag's BoolP silently accepts explicit values on no-argument boolean flags — rm --force=false target deletes the file, head --verbose=false runs — where GNU coreutils reject --flag=value before doing anything. df already worked around this with a private helper.

This promotes that helper to builtins.NoArgBool and migrates all 24 builtins to it, so the whole shell rejects --flag=value with the GNU wording (option '--force' doesn't allow an argument). Bare flags are unchanged.

It also fixes the custom pflag.Value boolean flags that the plain Bool/BoolP migration didn't cover and which silently accepted the explicit-value form:

  • grep -H/-h/-l/-L (orderedBoolFlag) — accepted --with-filename=true via strconv.ParseBool
  • strings -o (octalFlagVal) — ignored -o=x
  • xargs -0/--null (trackedBool) — accepted --null=true via strconv.ParseBool
  • head -q/--quiet/-v/--verbose (boolSeqFlag) — used a literal "true" NoOptDefVal sentinel, which is forgeable via =true, so --quiet=true was silently accepted (only --quiet=false was rejected)

All four now use the unforgeable builtins.NoArgSentinel (NUL byte) and route through the same flagparser rewrite, so they produce GNU's option '--foo' doesn't allow an argument. head additionally gains the precise per-flag wording it previously lacked.

Closes #538.

Out of scope (follow-ups): du's seqBool still intentionally accepts --flag=false. (pwd/cd already use a NUL sentinel and reject --flag=value correctly, with a generic option doesn't allow an argument message.)

🤖 Generated with Claude Code

julesmcrt and others added 2 commits June 23, 2026 16:05
pflag's BoolP silently accepts the explicit-value form (e.g.
rm --force=false target), where GNU coreutils reject it before
touching any operand. df already worked around this privately;
promote that helper to builtins.NoArgBool and migrate all 24
builtins to it so the whole shell matches GNU.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The no-argument migration converted Bool/BoolP flags to builtins.NoArgBool
but left the custom pflag.Value bool flags untouched, so they still
silently accepted the explicit-value form GNU getopt rejects:

  - grep  -H/-h/-l/-L (orderedBoolFlag) accepted --with-filename=true via
    strconv.ParseBool
  - strings -o (octalFlagVal) ignored -o=x
  - xargs  -0/--null (trackedBool) accepted --null=true via strconv.ParseBool
  - head   -q/--quiet/-v/--verbose (boolSeqFlag) used a literal "true"
    NoOptDefVal sentinel, which is forgeable via =true, so --quiet=true
    was silently accepted (only --quiet=false was rejected)

All four now use the unforgeable builtins.NoArgSentinel (NUL byte) and
return "flag does not allow an argument", which the flagparser rewrites to
GNU's "option '--foo' doesn't allow an argument". head additionally gains
the precise per-flag GNU wording it previously lacked.

pwd/cd already use a NUL sentinel and reject correctly; du's seqBool still
intentionally accepts --flag=false.

Adds reject scenarios for grep/strings/xargs and tightens head's existing
scenario to cover the previously-accepted =true case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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