-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
My urfave/cli version is
v3.7.0 (also reproduced on latest main at commit 4dc6aa6)
Checklist
- Are you running the latest v3 release? The list of releases is here.
- Did you check the manual for your release? The v3 manual is here
- Did you perform a search about this problem? Here's the GitHub guide about searching.
Dependency Management
- My project is using go modules.
- My project is using vendoring.
Describe the bug
--flag= (explicit empty value via =) is rejected with "flag needs an argument" instead of setting the flag to an empty string.
This is a side effect of the fix for #2223 in commit 2c77d6e. That fix correctly introduced valFromEqual to prevent --flag= from consuming the next argument. However, the error condition ended up slightly too broad (command_parse.go:167):
if flagVal == "" {
if len(rargs) == 1 || valFromEqual {
return ..., fmt.Errorf("%s%s", argumentNotProvidedErrMsg, firstArg)
}
flagVal = rargs[1]
...
}When valFromEqual is true and flagVal is "", the code enters the outer if (because flagVal == "") and then hits the error (because valFromEqual is true). But valFromEqual == true with flagVal == "" means the user wrote --flag= — they explicitly provided an empty value. This shouldn't be an error.
The original #2223 bug (consuming the next arg) is fully prevented by simply not entering the outer block when valFromEqual is true, since there's nothing to fetch:
if flagVal == "" && !valFromEqual {
if len(rargs) == 1 {
return ..., fmt.Errorf("%s%s", argumentNotProvidedErrMsg, firstArg)
}
flagVal = rargs[1]
...
}When valFromEqual is true, flagVal already holds the correct value (empty string), so the block is skipped entirely. The #2223 fix is preserved — no arg consumption happens.
| Input | Before #2223 | After #2223 | With this fix |
|---|---|---|---|
--a= --b=bar |
a="--b=bar" (bug) |
error | a="", b="bar" |
--a= |
a="" |
error | a="" |
--a=foo |
a="foo" |
a="foo" |
a="foo" |
--a (last arg) |
error | error | error |
To reproduce
package main
import (
"context"
"fmt"
"os"
cli "github.com/urfave/cli/v3"
)
func main() {
var name string
app := &cli.Command{
Name: "app",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Destination: &name,
},
},
Action: func(_ context.Context, cmd *cli.Command) error {
fmt.Printf("name: %q\n", cmd.String("name"))
fmt.Printf("args: %q\n", cmd.Args().Slice())
return nil
},
}
if err := app.Run(context.Background(), os.Args); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}go run . --name=Observed behavior
error: flag needs an argument: --name=
Expected behavior
name: ""
args: []
The flag should be set to an empty string. --flag= is standard POSIX/GNU syntax for explicitly setting a flag to empty. Go's standard flag package, cobra, and getopt all accept it.
Additional context
Test against latest main (4dc6aa6):
func TestFlagEqualsEmptyValue(t *testing.T) {
t.Run("--flag= sets empty string", func(t *testing.T) {
var val string
cmd := &Command{
Flags: []Flag{
&StringFlag{
Name: "name",
Destination: &val,
},
},
Action: func(_ context.Context, cmd *Command) error {
val = cmd.String("name")
return nil
},
}
err := cmd.Run(buildTestContext(t), []string{"app", "--name="})
assert.NoError(t, err)
assert.Equal(t, "", val)
})
t.Run("--flag= does not consume next positional arg", func(t *testing.T) {
var val string
var args []string
cmd := &Command{
Flags: []Flag{
&StringFlag{
Name: "name",
Destination: &val,
},
},
Action: func(_ context.Context, cmd *Command) error {
val = cmd.String("name")
args = cmd.Args().Slice()
return nil
},
}
err := cmd.Run(buildTestContext(t), []string{"app", "--name=", "positional"})
assert.NoError(t, err)
assert.Equal(t, "", val)
assert.Equal(t, []string{"positional"}, args)
})
}--- FAIL: TestFlagEqualsEmptyValue/--flag=_sets_empty_string (0.00s)
--- FAIL: TestFlagEqualsEmptyValue/--flag=_does_not_consume_next_positional_arg (0.00s)
Note: two existing test cases in TestFlagAction expect --f_string= to error with "flag needs an argument". With the fix, the empty string is accepted and passed to the flag's action validator, which returns "empty string". These expectations need updating:
// command_test.go
{
name: "flag_string_error",
args: []string{"app", "--f_string="},
err: "flag needs an argument: --f_string=", // → "empty string"
},
{
name: "flag_string_error2",
args: []string{"app", "--f_string=", "--f_bool"},
err: "flag needs an argument: --f_string=", // → "empty string"
},Want to fix this yourself?
Yes. The fix is a two-line change in command_parse.go that tightens the condition introduced in #2223 without reverting it, plus the test expectation updates above. I have a working patch ready.
Run go version and paste its output here
go version go1.26.1 linux/amd64
Run go env and paste its output here
n/a — bug is in urfave/cli internals, not environment-specific