Introduction

Cobra, the popular Go CLI library, provides MarkFlagRequired() to enforce that certain flags are provided by the user. However, the error messages, validation timing, and interaction with subcommands can be confusing. When required flag validation is not configured correctly, commands execute with missing configuration values, leading to cryptic downstream errors. Proper validation requires understanding when Cobra checks required flags (between PersistentPreRun and Run), how to add custom validation beyond presence checks, and how to provide helpful error messages.

Symptoms

Command runs with missing flags and fails with a cryptic error:

bash
$ myapp deploy
Error: database connection string is empty

Instead of the expected:

bash
$ myapp deploy
Error: required flag(s) "database-url" not set

Or validation runs too late, after expensive setup:

bash
$ myapp deploy --env production
Setting up infrastructure...
Creating resources...
Error: required flag "region" not set

Common Causes

  • MarkFlagRequired not called: The flag is defined but not marked as required
  • Required flag check happens after PreRun: Custom PreRun runs before required flag validation
  • Persistent flags vs local flags confusion: MarkFlagRequired on a persistent flag must be called on the root command
  • Custom validation not integrated: Required check only verifies presence, not value validity
  • Subcommand flag inheritance: Flags defined on parent command not automatically required on child
  • Flag default value bypasses required check: A default value satisfies the required check even when the user did not provide one

Step-by-Step Fix

Step 1: Use MarkFlagRequired correctly

```go var rootCmd = &cobra.Command{ Use: "myapp", Short: "My CLI application", }

var deployCmd = &cobra.Command{ Use: "deploy", Short: "Deploy the application", RunE: runDeploy, }

func init() { deployCmd.Flags().String("database-url", "", "Database connection URL") deployCmd.Flags().String("region", "", "Deployment region") deployCmd.Flags().String("env", "", "Environment name")

// Mark flags as required deployCmd.MarkFlagRequired("database-url") deployCmd.MarkFlagRequired("region") deployCmd.MarkFlagRequired("env")

rootCmd.AddCommand(deployCmd) } ```

Now the error is clear:

bash
$ myapp deploy
Error: required flag(s) "database-url", "env", "region" not set

Step 2: Add custom validation with PreRunE

```go var deployCmd = &cobra.Command{ Use: "deploy", Short: "Deploy the application", PreRunE: func(cmd *cobra.Command, args []string) error { // Validate flag values beyond presence region, _ := cmd.Flags().GetString("region") validRegions := map[string]bool{ "us-east-1": true, "us-west-2": true, "eu-west-1": true, } if !validRegions[region] { return fmt.Errorf("invalid region %q, must be one of: us-east-1, us-west-2, eu-west-1", region) }

env, _ := cmd.Flags().GetString("env") if env != "staging" && env != "production" { return fmt.Errorf("invalid environment %q, must be staging or production", env) }

return nil }, RunE: runDeploy, } ```

Cobra runs required flag checks before PreRunE, so missing flags are caught first.

Step 3: Use PersistentPreRunE for shared validation

```go var rootCmd = &cobra.Command{ Use: "myapp", Short: "My CLI application", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // Validate flags shared across all subcommands configPath, _ := cmd.Flags().GetString("config") if configPath != "" { if _, err := os.Stat(configPath); os.IsNotExist(err) { return fmt.Errorf("config file not found: %s", configPath) } } return nil }, }

func init() { rootCmd.PersistentFlags().String("config", "", "Path to config file") } ```

Prevention

  • Always pair Flags().String() with MarkFlagRequired() for mandatory flags
  • Use RunE instead of Run to return errors that Cobra formats properly
  • Add PreRunE for value validation (format, range, existence) beyond presence checks
  • Use MarkFlagFilename("config", "yaml", "yml") for file path flags
  • Test CLI commands in your test suite using ExecuteC with missing flags
  • Document required flags in the command's Long description and --help output