Introduction
Cobra is the standard CLI framework for Go, but without proper validation, commands accept invalid input and fail with cryptic errors deep in the execution path. Missing required flags, invalid flag values, and incorrect argument counts all lead to confusing user experiences. Cobra provides PreRun and PreRunE hooks for validation, but many CLI tools skip validation entirely and handle errors in the Run function, mixing validation logic with business logic and making it hard to provide helpful error messages.
Symptoms
``` Error: required flag(s) "config" not set Usage: myapp deploy [flags]
Flags: -c, --config string path to config file ```
Or silent wrong behavior:
# User passes invalid port, command proceeds with wrong value
myapp serve --port abc
# Starts on default port instead of failingCommon Causes
- No Required field on flags: Flags not marked as required
- Validation in Run instead of PreRunE: Mixed concerns, harder to test
- No argument count validation: Wrong number of positional args accepted
- No enum validation for flag values: Invalid enum values not rejected
- Silent fallback to defaults: Invalid input silently uses default value
- No suggestion for typos: User types --conifg instead of --config
Step-by-Step Fix
Step 1: Mark required flags and validate in PreRunE
```go import ( "fmt" "net" "strconv"
"github.com/spf13/cobra" )
var deployCmd = &cobra.Command{ Use: "deploy", Short: "Deploy application", Args: cobra.ExactArgs(1), // Exactly one positional argument PreRunE: func(cmd *cobra.Command, args []string) error { // Validate required flags config, _ := cmd.Flags().GetString("config") if config == "" { return fmt.Errorf("--config is required") }
// Validate port range port, _ := cmd.Flags().GetInt("port") if port < 1 || port > 65535 { return fmt.Errorf("--port must be between 1 and 65535, got %d", port) }
// Validate environment env, _ := cmd.Flags().GetString("environment") validEnvs := map[string]bool{"dev": true, "staging": true, "prod": true} if !validEnvs[env] { return fmt.Errorf("--environment must be dev, staging, or prod, got %q", env) }
return nil }, RunE: func(cmd *cobra.Command, args []string) error { // Business logic only - all validation done in PreRunE target := args[0] config, _ := cmd.Flags().GetString("config") port, _ := cmd.Flags().GetInt("port")
return doDeploy(target, config, port) }, }
func init() { deployCmd.Flags().StringP("config", "c", "", "config file path") deployCmd.Flags().IntP("port", "p", 8080, "server port") deployCmd.Flags().StringP("environment", "e", "dev", "deployment environment")
// Mark flags as required deployCmd.MarkFlagRequired("config")
// Add flag suggestions deployCmd.RegisterFlagCompletionFunc("environment", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{"dev", "staging", "prod"}, cobra.ShellCompDirectiveNoFileComp }) } ```
Step 2: Validate IP addresses and network values
func validateAddress(cmd *cobra.Command, args []string) error {
addr, _ := cmd.Flags().GetString("address")
if addr != "" {
if ip := net.ParseIP(addr); ip == nil {
return fmt.Errorf("--address %q is not a valid IP address", addr)
}
}
return nil
}Step 3: Add custom error messages with suggestions
```go func init() { // Customize error output rootCmd.SetErrPrefix("Error:") rootCmd.SilenceErrors = true // Handle errors ourselves rootCmd.SilenceUsage = true // Only show usage on user error
rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { return fmt.Errorf("invalid flag: %w\n\nUse --help for usage information", err) }) } ```
Prevention
- Use PreRunE for all validation, keep RunE for business logic
- Mark flags as required with MarkFlagRequired() when they must be provided
- Validate flag values, not just presence -- check ranges, formats, enums
- Use cobra.ExactArgs, cobra.MinimumNArgs for positional argument validation
- Register tab completion functions to prevent user typos
- Add integration tests that verify error messages for invalid input
- Use SilenceErrors and custom error formatting for consistent UX