Skip to content

GenericFlag validators panic with standard-library flag.Getter values #2459

Closed

Description

@MohammediYunus

My urfave/cli version is

Reproduced on the latest release, v3.14.0, and current main at 26bd9a2.

Checklist

  • Reproduced on the latest v3 release.
  • Checked the v3 manual and the Value/GenericFlag interfaces.
  • Searched existing issues and PRs for GenericFlag, Getter and validator failures.

Dependency Management

Go modules.

Describe the bug

A GenericFlag can accept a value from Go's standard flag package, then panic before calling its Validator. The value satisfies cli.Value, but its Get method returns a string. GenericFlag unwraps that string and then tries to assert it back to cli.Value.

To reproduce

Save this as main.go:

package main

import (
	"context"
	"flag"

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

func main() {
	std := flag.NewFlagSet("std", flag.ContinueOnError)
	std.String("value", "", "")

	cmd := &cli.Command{
		Flags: []cli.Flag{&cli.GenericFlag{
			Name:      "value",
			Value:     std.Lookup("value").Value.(cli.Value),
			Validator: func(cli.Value) error { return nil },
		}},
	}
	_ = cmd.Run(context.Background(), []string{"app", "--value", "hello"})
}

Run in an empty directory:

go mod init example.com/cli-getter-repro
go get github.com/urfave/cli/[email protected]
go run .

Observed behavior

panic: interface conversion: string is not cli.Value: missing method Get

The panic occurs at the typed Validator call in FlagBase.Set. The Validator body is never reached.

On current main, I also checked these related paths with the standard-library value and a custom string Getter:

  • ValidateDefaults and the flag Action panic with the same type mismatch.
  • Without callbacks, parsing succeeds, but Command.Generic returns nil for the registered name and alias.
  • A control Getter that returns itself works in all four cases.

Expected behavior

A value satisfying cli.Value should work with the declared cli.Value callbacks without an internal type-assertion panic. Command.Generic should be able to retrieve the registered value after successful parsing.

Additional context

cli.Value embeds flag.Getter, whose Get result can be the underlying value. The generic adapter returns that payload, but FlagBase.Set expects cli.Value for this flag type. The existing Parser test fixture returns itself from Get, which hides the mismatch.

Returning the supplied value directly from the adapter would fix these paths, but it would also change what GenericFlag.Get and Command.Value return. That could affect existing callers.

Should callbacks and Command.Generic receive the supplied cli.Value while Command.Value keeps returning its Get result, or should GenericFlag.Get consistently expose the supplied value object?

Want to fix this yourself?

Yes. I can add the regression tests and work on a fix once the intended retrieval behavior is clear.

Go version and relevant environment

go version go1.26.5 darwin/arm64
GOOS=darwin
GOARCH=arm64

The examples were run locally on both versions above; no fix has been submitted yet.

Activity

  1. coilysiren commented on Oct 7, 2026

    @coilysiren
    Member

    @MohammediYunus this makes sense! Submit a PR at will

  2. dearchap commented on Oct 7, 2026

    @dearchap
    Contributor

    @MohammediYunus Actually I'm not sure what you are trying to do here. You can trying to use the flag.Value from the std library as a cli.Value for urfave/cli. That wont work.

  3. MohammediYunus commented on Oct 7, 2026

    @MohammediYunus
    ContributorAuthor

    Thanks @coilysiren!

    @dearchap, I should have made the distinction clearer. A flag.Value alone isn't enough. The value in that example also implements flag.Getter, so the cli.Value assertion succeeds. The panic happens later in FlagBase.Set.

    Here's the same case with a custom type, without the standard flag package or a runtime type assertion:

    package main
    
    import (
    	"context"
    
    	cli "github.com/urfave/cli/v3"
    )
    
    type textValue struct{ text string }
    
    func (v *textValue) String() string     { return v.text }
    func (v *textValue) Set(s string) error { v.text = s; return nil }
    func (v *textValue) Get() any           { return v.text }
    
    var _ cli.Value = (*textValue)(nil)
    
    func main() {
    	cmd := &cli.Command{
    		Flags: []cli.Flag{&cli.GenericFlag{
    			Name:      "value",
    			Value:     &textValue{},
    			Validator: func(cli.Value) error { return nil },
    		}},
    	}
    	_ = cmd.Run(context.Background(), []string{"app", "--value", "hello"})
    }

    I ran this on v3.14.0 and main at 26bd9a2. Both panic before entering the validator. Changing only Get to return v makes both pass.

    Is Get expected to return a cli.Value, such as the value itself, rather than its underlying payload? If that's the intended contract, I'm happy to update the report and scope the PR accordingly. I want to avoid changing Command.Value's existing behaviour unnecessarily.

  4. coilysiren commented on Oct 7, 2026

    @coilysiren
    Member

    My impression is that you're satisfying the interface but the validator is rejecting it a few steps down, so the solve is to either tighten the interface or loosen the validator. Im not opinionated on which one is better.

    (on a train so skimming, this might not be a fully correct read)

  5. MohammediYunus commented on Oct 7, 2026

    @MohammediYunus
    ContributorAuthor

    Thanks for taking a look! I’ve opened #2460 with a fix that keeps the existing interface and Command.Value behaviour. It handles payload-returning Getters in validation, flag actions and Command.Generic, with regression tests for each. Happy to adjust after review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions