Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Empty positional arguments terminate parse loop, dropping remaining args #2292

Copy link
Copy link

Description

@idelchi
Issue body actions

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

Empty or whitespace-only positional arguments cause parseFlags() to stop processing all remaining arguments. Any args after (and including) the empty one are silently dropped.

The cause is in command_parse.go:80-82. The parser does strings.TrimSpace(rargs[0]) and then breaks out of the parse loop when the result is empty:

firstArg := strings.TrimSpace(rargs[0])
if len(firstArg) == 0 {
    break
}

So app hello "" world yields ["hello"] — the empty string and everything after it are lost. Same for whitespace-only args like " ", since TrimSpace reduces them to empty before the check.

To reproduce

package main

import (
    "context"
    "fmt"
    "os"

    cli "github.com/urfave/cli/v3"
)

func main() {
    app := &cli.Command{
        Name: "app",
        Action: func(_ context.Context, cmd *cli.Command) error {
            fmt.Printf("args: %q (len=%d)\n", cmd.Args().Slice(), cmd.Args().Len())
            return nil
        },
    }

    app.Run(context.Background(), os.Args)
}
go run . hello "" world

Observed behavior

args: ["hello"] (len=1)

The empty string and world are silently dropped.

Expected behavior

args: ["hello" "" "world"] (len=3)

All three positional arguments should be preserved. Empty strings are valid arguments — the shell delivered them, and the parser should not discard them. Go's standard flag package, cobra, and Python's argparse all preserve them.

Additional context

Test against latest main (4dc6aa6):

func TestEmptyPositionalArgs(t *testing.T) {
    t.Run("empty arg between values", func(t *testing.T) {
        var args []string

        cmd := &Command{
            Action: func(_ context.Context, cmd *Command) error {
                args = cmd.Args().Slice()
                return nil
            },
        }

        err := cmd.Run(buildTestContext(t), []string{"app", "hello", "", "world"})
        assert.NoError(t, err)
        assert.Equal(t, []string{"hello", "", "world"}, args)
    })

    t.Run("empty arg at start", func(t *testing.T) {
        var args []string

        cmd := &Command{
            Action: func(_ context.Context, cmd *Command) error {
                args = cmd.Args().Slice()
                return nil
            },
        }

        err := cmd.Run(buildTestContext(t), []string{"app", "", "hello"})
        assert.NoError(t, err)
        assert.Equal(t, []string{"", "hello"}, args)
    })

    t.Run("whitespace-only arg", func(t *testing.T) {
        var args []string

        cmd := &Command{
            Action: func(_ context.Context, cmd *Command) error {
                args = cmd.Args().Slice()
                return nil
            },
        }

        err := cmd.Run(buildTestContext(t), []string{"app", "hello", "  ", "world"})
        assert.NoError(t, err)
        assert.Equal(t, []string{"hello", "  ", "world"}, args)
    })
}
--- FAIL: TestEmptyPositionalArgs/empty_arg_between_values (0.00s)
--- FAIL: TestEmptyPositionalArgs/empty_arg_at_start (0.00s)
--- FAIL: TestEmptyPositionalArgs/whitespace-only_arg (0.00s)

The fix is to collect the original (untrimmed) argument as a positional arg and continue the loop instead of breaking:

if len(firstArg) == 0 {
    posArgs = append(posArgs, rargs[0])
    continue
}

Note: several existing test cases in defaultCommandTests, defaultCommandSubCommandTests, and defaultCommandFlagTests use " " (whitespace-only) as the command name and expect an error. These encode the current (broken) behavior where " " terminates parsing. With the fix, " " is correctly treated as a positional argument rather than aborting the parse loop, so these expectations need to flip from true (error) to false (no error):

// defaultCommandTests
{" ", "", nil, true},   // → false

// defaultCommandSubCommandTests
{" ", "jimmers", "foobar", true},  // → false
{" ", "", "", true},               // → false
{" ", "j", "", true},              // → false

// defaultCommandFlagTests
{" ", "-j", "foobar", true},  // → false
{" ", "", "", true},           // → false
{" ", "-j", "", true},         // → false

Want to fix this yourself?

Yes. The fix is a two-line change in command_parse.go 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
Reactions are currently unavailable

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/v3relates to / is being considered for v3relates to / is being considered for v3kind/bugdescribes or fixes a bugdescribes or fixes a bugstatus/triagemaintainers still need to look into thismaintainers still need to look into this

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    Morty Proxy This is a proxified and sanitized view of the page, visit original site.