Skip to content

Flag Action should be called when value is set by alternative sources.  #2041

Closed

Description

@project0

Checklist

  • Are you running the latest v3 release? The list of releases is here.
    v3.0.0-beta1
  • Did you check the manual for your release? The v3 manual is here.
  • Did you perform a search about this feature? Here's the GitHub guide about searching.

What problem does this solve?

Fix action beeing called for all circumstances when a value is set by either a flag or alternative source. Currently the flag action is only executed when given as argument.

example:

		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    flag_log_level,
				Usage:   "The log level (trace, debug, info, warn, error, fatal, panic)",
				Value:   "info",
				Sources: cli.EnvVars(flagEnv(flag_log_level)),
				Action: func(ctx context.Context, cmd *cli.Command, value string) error {
					log.Info("Setting log level to ", value)
					level, err := log.ParseLevel(value)
					if err != nil {
						return err
					}
					log.SetLevel(level)
					return nil
				},
			},

Solution description

When a env var is set, the action should be called.

Describe alternatives you've considered

not using in this case as it is has no consistent experience for users.

Activity

  1. added
    area/v3relates to / is being considered for v3
    status/triagemaintainers still need to look into this
    on Jan 6, 2025
  2. changed the title [-]Flag Action should be called when value is by alternative sources. [/-] [+]Flag Action should be called when value is set by alternative sources. [/+] on Jan 6, 2025
  3. dearchap commented on Jan 12, 2025

    @dearchap
    Contributor

    @project0 Can you set Local: true for cli.StringFlag and try ?

  4. project0 commented on Jan 12, 2025

    @project0
    Author

    @dearchap indeed, setting Local true populates the value. Is this the intended behavior? If so, documenting it would be beneficial in this case.

  5. dearchap commented on Jan 12, 2025

    @dearchap
    Contributor

    The problem is that by default we arent running FlagAction for "non local" flags. Flags which are "non local" are available to all subcommands as well and there was a discussion as to how flag actions should be run for these kinds of flags. Local means that the flag is local only to current command and will have its flag actions run. Let me think how to proceed with this.

    cc @urfave/cli

  6. dearchap commented on Jan 12, 2025

    @dearchap
    Contributor

    In this case suppose your flag is "non local" you could invoke it something like this

    root cmd subcmd --log-level debug
    

    we can run action for all flags(including log level ) for subcmd

    but say you invoke it like this

    LOG_LEVEL=debug root cmd subcmd
    

    the log-level flag is set from environment when should the flag action be run ? or for example

    LOG_LEVEL=debug root cmd --log-level info subcmd
    

    It starts getting complicated.

  7. adamcohen2 commented on Apr 17, 2025

    @adamcohen2

    I've run into this same situation. This seems to be an undocumented breaking change in behaviour between v2 and v3:

    • github.com/urfave/cli/v2

      Click to expand code
      package main
      
      import (
      	"fmt"
      	"log"
      	"os"
      
      	"github.com/urfave/cli/v2"
      )
      
      func main() {
      	app := cli.NewApp()
      	app.Commands = []*cli.Command{
      		{
      			Name: "serve",
      			Flags: []cli.Flag{
      				&cli.StringFlag{
      					Name:     "custom-var",
      					EnvVars:  []string{"CUSTOM_VAR"},
      					Required: false,
      					Action: func(_ *cli.Context, s string) error {
      						fmt.Println("CUSTOM_VAR Action")
      						return nil
      					},
      				},
      			},
      			Action: func(ctx *cli.Context) error {
      				fmt.Println("Serve")
      				return nil
      			},
      		},
      	}
      
      	if err := app.Run(os.Args); err != nil {
      		log.Fatal(err)
      	}
      }

      Output:

      go run . serve
      CUSTOM_VAR Action
      Serve
    • github.com/urfave/cli/v3

      Click to expand code
      package main
      
      import (
      	"context"
      	"fmt"
      	"log"
      	"os"
      
      	"github.com/urfave/cli/v3"
      )
      
      func main() {
      	app := &cli.Command{
      		Name: "my app",
      		Commands: []*cli.Command{
      			{
      				Name: "serve",
      				Flags: []cli.Flag{
      					&cli.StringFlag{
      						Name:     "custom-var",
      						Sources:  cli.EnvVars("CUSTOM_VAR"),
      						Required: false,
      						Action: func(_ context.Context, _ *cli.Command, s string) error {
      							fmt.Println("CUSTOM_VAR Action")
      							return nil
      						},
      					},
      				},
      				Action: func(_ context.Context, cmd *cli.Command) error {
      					fmt.Println("Serve")
      					return nil
      				},
      			},
      		},
      	}
      
      	if err := app.Run(context.Background(), os.Args); err != nil {
      		log.Fatal(err)
      	}
      }

      Output:

      go run . serve
      Serve

    I've also tried adding Local: true to &cli.StringFlag, but this doesn't change the behaviour:

    Click to expand code
    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"os"
    
    	"github.com/urfave/cli/v3"
    )
    
    func main() {
    	app := &cli.Command{
    		Name: "my app",
    		Commands: []*cli.Command{
    			{
    				Name: "serve",
    				Flags: []cli.Flag{
    					&cli.StringFlag{
    						Name:     "custom-var",
    						Sources:  cli.EnvVars("CUSTOM_VAR"),
    						Local: true,
    						Required: false,
    						Action: func(_ context.Context, _ *cli.Command, s string) error {
    							fmt.Println("CUSTOM_VAR Action")
    							return nil
    						},
    					},
    				},
    				Action: func(_ context.Context, cmd *cli.Command) error {
    					fmt.Println("Serve")
    					return nil
    				},
    			},
    		},
    	}
    
    	if err := app.Run(context.Background(), os.Args); err != nil {
    		log.Fatal(err)
    	}
    }

    Output:

    go run . serve
    Serve

    I have the following questions:

    1. Is there a reason this change of behaviour isn't documented in the v2-to-v3 migration guide?
    2. It was mentioned here that a workaround is to set Local: true, why doesn't it work in the above example?

    Thanks!

  8. added
    kind/bugdescribes or fixes a bug
    and removed
    status/triagemaintainers still need to look into this
    on Jun 14, 2025
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

    area/v3relates to / is being considered for v3kind/bugdescribes or fixes a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions