diff --git a/args.go b/args.go
index bd65c17bde..ee132bf89b 100644
--- a/args.go
+++ b/args.go
@@ -16,39 +16,42 @@ type Args interface {
 	Slice() []string
 }
 
-type args []string
+type stringSliceArgs struct {
+	v []string
+}
 
-func (a *args) Get(n int) string {
-	if len(*a) > n {
-		return (*a)[n]
+func (a *stringSliceArgs) Get(n int) string {
+	if len(a.v) > n {
+		return a.v[n]
 	}
 	return ""
 }
 
-func (a *args) First() string {
+func (a *stringSliceArgs) First() string {
 	return a.Get(0)
 }
 
-func (a *args) Tail() []string {
+func (a *stringSliceArgs) Tail() []string {
 	if a.Len() >= 2 {
-		tail := []string((*a)[1:])
+		tail := a.v[1:]
 		ret := make([]string, len(tail))
 		copy(ret, tail)
 		return ret
 	}
+
 	return []string{}
 }
 
-func (a *args) Len() int {
-	return len(*a)
+func (a *stringSliceArgs) Len() int {
+	return len(a.v)
 }
 
-func (a *args) Present() bool {
+func (a *stringSliceArgs) Present() bool {
 	return a.Len() != 0
 }
 
-func (a *args) Slice() []string {
-	ret := make([]string, len(*a))
-	copy(ret, *a)
+func (a *stringSliceArgs) Slice() []string {
+	ret := make([]string, len(a.v))
+	copy(ret, a.v)
 	return ret
 }
diff --git a/command.go b/command.go
index e2a0e212c5..b032239153 100644
--- a/command.go
+++ b/command.go
@@ -12,8 +12,14 @@ import (
 	"strings"
 )
 
-// ignoreFlagPrefix is to ignore test flags when adding flags from other packages
-const ignoreFlagPrefix = "test."
+const (
+	// ignoreFlagPrefix is to ignore test flags when adding flags from other packages
+	ignoreFlagPrefix = "test."
+
+	commandContextKey = contextKey("cli.context")
+)
+
+type contextKey string
 
 // Command contains everything needed to run an application that
 // accepts a string slice of arguments such as os.Args. A given
@@ -127,6 +133,8 @@ type Command struct {
 	// The parent of this command. This value will be nil for the
 	// command at the root of the graph.
 	parent *Command
+	// the flag.FlagSet for this command
+	flagSet *flag.FlagSet
 	// track state of error handling
 	isInError bool
 	// track state of defaults
@@ -156,59 +164,60 @@ func (cmd *Command) Command(name string) *Command {
 	return nil
 }
 
-func (cmd *Command) setupDefaults(arguments []string) {
+func (cmd *Command) setupDefaults(osArgs []string) {
 	if cmd.didSetupDefaults {
-		tracef("already did setup")
+		tracef("already did setup (cmd=%[1]q)", cmd.Name)
 		return
 	}
 
 	cmd.didSetupDefaults = true
 
 	isRoot := cmd.parent == nil
-	tracef("isRoot? %[1]v", isRoot)
+	tracef("isRoot? %[1]v (cmd=%[2]q)", isRoot, cmd.Name)
 
 	if cmd.ShellComplete == nil {
-		tracef("setting default ShellComplete")
+		tracef("setting default ShellComplete (cmd=%[1]q)", cmd.Name)
 		cmd.ShellComplete = DefaultCompleteWithFlags(cmd)
 	}
 
 	if cmd.Name == "" && isRoot {
-		tracef("setting cmd.Name from first arg basename")
-		cmd.Name = filepath.Base(arguments[0])
+		name := filepath.Base(osArgs[0])
+		tracef("setting cmd.Name from first arg basename (cmd=%[1]q)", name)
+		cmd.Name = name
 	}
 
 	if cmd.Usage == "" && isRoot {
-		tracef("setting default Usage")
+		tracef("setting default Usage (cmd=%[1]q)", cmd.Name)
 		cmd.Usage = "A new cli application"
 	}
 
 	if cmd.Version == "" {
-		tracef("setting HideVersion=true due to empty Version")
+		tracef("setting HideVersion=true due to empty Version (cmd=%[1]q)", cmd.Name)
 		cmd.HideVersion = true
 	}
 
 	if cmd.Action == nil {
-		tracef("setting default Action as help command action")
+		tracef("setting default Action as help command action (cmd=%[1]q)", cmd.Name)
 		cmd.Action = helpCommandAction
 	}
 
 	if cmd.Reader == nil {
-		tracef("setting default Reader as os.Stdin")
+		tracef("setting default Reader as os.Stdin (cmd=%[1]q)", cmd.Name)
 		cmd.Reader = os.Stdin
 	}
 
 	if cmd.Writer == nil {
-		tracef("setting default Writer as os.Stdout")
+		tracef("setting default Writer as os.Stdout (cmd=%[1]q)", cmd.Name)
 		cmd.Writer = os.Stdout
 	}
 
 	if cmd.ErrWriter == nil {
-		tracef("setting default ErrWriter as os.Stderr")
+		tracef("setting default ErrWriter as os.Stderr (cmd=%[1]q)", cmd.Name)
 		cmd.ErrWriter = os.Stderr
 	}
 
 	if cmd.AllowExtFlags {
-		tracef("visiting all flags given AllowExtFlags=true")
+		tracef("visiting all flags given AllowExtFlags=true (cmd=%[1]q)", cmd.Name)
 		// add global flags added by other packages
 		flag.VisitAll(func(f *flag.Flag) {
 			// skip test flags
@@ -219,20 +228,19 @@ func (cmd *Command) setupDefaults(arguments []string) {
 	}
 
 	for _, subCmd := range cmd.Commands {
-		tracef("setting sub-command parent as self")
+		tracef("setting sub-command (cmd=%[1]q) parent as self (cmd=%[2]q)", subCmd.Name, cmd.Name)
 		subCmd.parent = cmd
 	}
 
-	tracef("ensuring help command and flag")
 	cmd.ensureHelp()
 
 	if !cmd.HideVersion && isRoot {
-		tracef("appending version flag")
+		tracef("appending version flag (cmd=%[1]q)", cmd.Name)
 		cmd.appendFlag(VersionFlag)
 	}
 
 	if cmd.PrefixMatchCommands && cmd.SuggestCommandFunc == nil {
-		tracef("setting default SuggestCommandFunc")
+		tracef("setting default SuggestCommandFunc (cmd=%[1]q)", cmd.Name)
 		cmd.SuggestCommandFunc = suggestCommand
 	}
 
@@ -240,82 +248,88 @@ func (cmd *Command) setupDefaults(arguments []string) {
 		completionCommand := buildCompletionCommand()
 
 		if cmd.ShellCompletionCommandName != "" {
-			tracef("setting completion command name from ShellCompletionCommandName")
+			tracef(
+				"setting completion command name (%[1]q) from "+
+					"cmd.ShellCompletionCommandName (cmd=%[2]q)",
+				cmd.ShellCompletionCommandName, cmd.Name,
+			)
 			completionCommand.Name = cmd.ShellCompletionCommandName
 		}
 
-		tracef("appending completionCommand")
+		tracef("appending completionCommand (cmd=%[1]q)", cmd.Name)
 		cmd.appendCommand(completionCommand)
 	}
 
-	tracef("setting command categories")
+	tracef("setting command categories (cmd=%[1]q)", cmd.Name)
 	cmd.categories = newCommandCategories()
 
 	for _, subCmd := range cmd.Commands {
 		cmd.categories.AddCommand(subCmd.Category, subCmd)
 	}
 
-	tracef("sorting command categories")
+	tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
 	sort.Sort(cmd.categories.(*commandCategories))
 
-	tracef("setting flag categories")
+	tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
 	cmd.flagCategories = newFlagCategoriesFromFlags(cmd.Flags)
 
 	if cmd.Metadata == nil {
-		tracef("setting default Metadata")
+		tracef("setting default Metadata (cmd=%[1]q)", cmd.Name)
 		cmd.Metadata = map[string]any{}
 	}
 
 	if len(cmd.SliceFlagSeparator) != 0 {
-		tracef("setting defaultSliceFlagSeparator from cmd.SliceFlagSeparator")
+		tracef("setting defaultSliceFlagSeparator from cmd.SliceFlagSeparator (cmd=%[1]q)", cmd.Name)
 		defaultSliceFlagSeparator = cmd.SliceFlagSeparator
 	}
 
-	tracef("setting disableSliceFlagSeparator from cmd.DisableSliceFlagSeparator")
+	tracef("setting disableSliceFlagSeparator from cmd.DisableSliceFlagSeparator (cmd=%[1]q)", cmd.Name)
 	disableSliceFlagSeparator = cmd.DisableSliceFlagSeparator
 }
 
-func (cmd *Command) setupCommandGraph(cCtx *Context) {
+func (cmd *Command) setupCommandGraph() {
+	tracef("setting up command graph (cmd=%[1]q)", cmd.Name)
+
 	for _, subCmd := range cmd.Commands {
 		subCmd.parent = cmd
-		subCmd.setupSubcommand(cCtx)
-		subCmd.setupCommandGraph(cCtx)
+		subCmd.setupSubcommand()
+		subCmd.setupCommandGraph()
 	}
 }
 
-func (cmd *Command) setupSubcommand(cCtx *Context) {
-	cmd.ensureHelp()
+func (cmd *Command) setupSubcommand() {
+	tracef("setting up self as sub-command (cmd=%[1]q)", cmd.Name)
 
-	if cCtx.Command.UseShortOptionHandling {
-		cmd.UseShortOptionHandling = true
-	}
+	cmd.ensureHelp()
 
-	tracef("setting command categories")
+	tracef("setting command categories (cmd=%[1]q)", cmd.Name)
 	cmd.categories = newCommandCategories()
 
 	for _, subCmd := range cmd.Commands {
 		cmd.categories.AddCommand(subCmd.Category, subCmd)
 	}
 
-	tracef("sorting command categories")
+	tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
 	sort.Sort(cmd.categories.(*commandCategories))
 
-	tracef("setting flag categories")
+	tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
 	cmd.flagCategories = newFlagCategoriesFromFlags(cmd.Flags)
 }
 
 func (cmd *Command) ensureHelp() {
+	tracef("ensuring help (cmd=%[1]q)", cmd.Name)
+
 	helpCommand := buildHelpCommand(true)
 
 	if cmd.Command(helpCommand.Name) == nil && !cmd.HideHelp {
 		if !cmd.HideHelpCommand {
-			tracef("appending helpCommand")
+			tracef("appending helpCommand (cmd=%[1]q)", cmd.Name)
 			cmd.appendCommand(helpCommand)
 		}
 	}
 
 	if HelpFlag != nil && !cmd.HideHelp {
-		tracef("appending HelpFlag")
+		tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name)
 		cmd.appendFlag(HelpFlag)
 	}
 }
@@ -323,12 +337,13 @@ func (cmd *Command) ensureHelp() {
 // Run is the entry point to the command graph. The positional
 // arguments are parsed according to the Flag and Command
 // definitions and the matching Action functions are run.
-func (cmd *Command) Run(ctx context.Context, arguments []string) (deferErr error) {
-	cmd.setupDefaults(arguments)
+func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
+	tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
+	cmd.setupDefaults(osArgs)
 
-	parentContext := &Context{Context: ctx}
-	if v, ok := ctx.Value(contextContextKey).(*Context); ok {
-		parentContext = v
+	if v, ok := ctx.Value(commandContextKey).(*Command); ok {
+		tracef("setting parent (cmd=%[1]q) command from context.Context value (cmd=%[2]q)", v.Name, cmd.Name)
+		cmd.parent = v
 	}
 
 	// handle the completion flag separately from the flagset since
@@ -337,52 +352,53 @@ func (cmd *Command) Run(ctx context.Context, arguments []string) (deferErr error
 	// flag name as the value of the flag before it which is undesirable
 	// note that we can only do this because the shell autocomplete function
 	// always appends the completion flag at the end of the command
-	shellComplete, arguments := checkShellCompleteFlag(cmd, arguments)
+	enableShellCompletion, osArgs := checkShellCompleteFlag(cmd, osArgs)
 
-	cCtx := NewContext(cmd, nil, parentContext)
-	cCtx.shellComplete = shellComplete
+	tracef("setting cmd.EnableShellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", enableShellCompletion, cmd.Name)
+	cmd.EnableShellCompletion = enableShellCompletion
 
-	cCtx.Command = cmd
+	tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
 
-	ctx = context.WithValue(ctx, contextContextKey, cCtx)
+	tracef("setting self as cmd in context (cmd=%[1]q)", cmd.Name)
+	ctx = context.WithValue(ctx, commandContextKey, cmd)
 
 	if cmd.parent == nil {
-		cmd.setupCommandGraph(cCtx)
+		cmd.setupCommandGraph()
 	}
 
-	a := args(arguments)
-	set, err := cmd.parseFlags(&a, cCtx)
-	cCtx.flagSet = set
+	args, err := cmd.parseFlags(&stringSliceArgs{v: osArgs})
+
+	tracef("using post-parse arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
 
-	if checkCompletions(cCtx) {
+	if checkCompletions(ctx, cmd) {
 		return nil
 	}
 
 	if err != nil {
-		tracef("setting deferErr from %[1]v", err)
+		tracef("setting deferErr from %[1]q (cmd=%[2]q)", err, cmd.Name)
 		deferErr = err
 
-		cCtx.Command.isInError = true
+		cmd.isInError = true
 		if cmd.OnUsageError != nil {
-			err = cmd.OnUsageError(cCtx, err, cmd.parent != nil)
-			err = cCtx.Command.handleExitCoder(cCtx, err)
+			err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
+			err = cmd.handleExitCoder(ctx, err)
 			return err
 		}
-		_, _ = fmt.Fprintf(cCtx.Command.Root().ErrWriter, "%s %s\n\n", "Incorrect Usage:", err.Error())
-		if cCtx.Command.Suggest {
+		fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
+		if cmd.Suggest {
 			if suggestion, err := cmd.suggestFlagFromError(err, ""); err == nil {
-				fmt.Fprintf(cCtx.Command.Root().ErrWriter, "%s", suggestion)
+				fmt.Fprintf(cmd.Root().ErrWriter, "%s", suggestion)
 			}
 		}
 		if !cmd.HideHelp {
 			if cmd.parent == nil {
 				tracef("running ShowAppHelp")
-				if err := ShowAppHelp(cCtx); err != nil {
-					tracef("SILENTLY IGNORING ERROR running ShowAppHelp %[1]v", err)
+				if err := ShowAppHelp(cmd); err != nil {
+					tracef("SILENTLY IGNORING ERROR running ShowAppHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
 				}
 			} else {
 				tracef("running ShowCommandHelp with %[1]q", cmd.Name)
-				if err := ShowCommandHelp(cCtx.parent, cmd.Name); err != nil {
+				if err := ShowCommandHelp(ctx, cmd, cmd.Name); err != nil {
 					tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err)
 				}
 			}
@@ -391,19 +407,21 @@ func (cmd *Command) Run(ctx context.Context, arguments []string) (deferErr error
 		return err
 	}
 
-	if checkHelp(cCtx) {
-		return helpCommandAction(cCtx)
+	if cmd.checkHelp() {
+		return helpCommandAction(ctx, cmd)
+	} else {
+		tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
 	}
 
-	if cmd.parent == nil && !cCtx.Command.HideVersion && checkVersion(cCtx) {
-		ShowVersion(cCtx)
+	if cmd.parent == nil && !cmd.HideVersion && checkVersion(cmd) {
+		ShowVersion(cmd)
 		return nil
 	}
 
-	if cmd.After != nil && !cCtx.shellComplete {
+	if cmd.After != nil && !cmd.EnableShellCompletion {
 		defer func() {
-			if err := cmd.After(cCtx); err != nil {
-				err = cCtx.Command.handleExitCoder(cCtx, err)
+			if err := cmd.After(ctx, cmd); err != nil {
+				err = cmd.handleExitCoder(ctx, err)
 
 				if deferErr != nil {
 					deferErr = newMultiError(deferErr, err)
@@ -414,41 +432,48 @@ func (cmd *Command) Run(ctx context.Context, arguments []string) (deferErr error
 		}()
 	}
 
-	if err := cCtx.checkRequiredFlags(cmd.Flags); err != nil {
-		cCtx.Command.isInError = true
-		_ = ShowSubcommandHelp(cCtx)
+	if err := cmd.checkRequiredFlags(); err != nil {
+		cmd.isInError = true
+		_ = ShowSubcommandHelp(cmd)
 		return err
 	}
 
 	for _, grp := range cmd.MutuallyExclusiveFlags {
-		if err := grp.check(cCtx); err != nil {
-			_ = ShowSubcommandHelp(cCtx)
+		if err := grp.check(cmd); err != nil {
+			_ = ShowSubcommandHelp(cmd)
 			return err
 		}
 	}
 
-	if cmd.Before != nil && !cCtx.shellComplete {
-		if err := cmd.Before(cCtx); err != nil {
-			deferErr = cCtx.Command.handleExitCoder(cCtx, err)
+	if cmd.Before != nil && !cmd.EnableShellCompletion {
+		if err := cmd.Before(ctx, cmd); err != nil {
+			deferErr = cmd.handleExitCoder(ctx, err)
 			return deferErr
 		}
 	}
 
-	if err := runFlagActions(cCtx, cmd.appliedFlags); err != nil {
+	tracef("running flag actions (cmd=%[1]q)", cmd.Name)
+
+	if err := runFlagActions(ctx, cmd, cmd.appliedFlags); err != nil {
 		return err
 	}
 
 	var subCmd *Command
-	args := cCtx.Args()
+
 	if args.Present() {
+		tracef("checking positional args %[1]q (cmd=%[2]q)", args, cmd.Name)
+
 		name := args.First()
-		if cCtx.Command.SuggestCommandFunc != nil {
-			name = cCtx.Command.SuggestCommandFunc(cmd.Commands, name)
+
+		tracef("using first positional argument as sub-command name=%[1]q (cmd=%[2]q)", name, cmd.Name)
+
+		if cmd.SuggestCommandFunc != nil {
+			name = cmd.SuggestCommandFunc(cmd.Commands, name)
 		}
 		subCmd = cmd.Command(name)
 		if subCmd == nil {
-			hasDefault := cCtx.Command.DefaultCommand != ""
-			isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames())
+			hasDefault := cmd.DefaultCommand != ""
+			isFlagName := checkStringSliceIncludes(name, cmd.FlagNames())
 
 			var (
 				isDefaultSubcommand   = false
@@ -456,7 +481,7 @@ func (cmd *Command) Run(ctx context.Context, arguments []string) (deferErr error
 			)
 
 			if hasDefault {
-				dc := cCtx.Command.Command(cCtx.Command.DefaultCommand)
+				dc := cmd.Command(cmd.DefaultCommand)
 				defaultHasSubcommands = len(dc.Commands) > 0
 				for _, dcSub := range dc.Commands {
 					if checkStringSliceIncludes(name, dcSub.Names()) {
@@ -467,42 +492,58 @@ func (cmd *Command) Run(ctx context.Context, arguments []string) (deferErr error
 			}
 
 			if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) {
-				argsWithDefault := cCtx.Command.argsWithDefaultCommand(args)
+				argsWithDefault := cmd.argsWithDefaultCommand(args)
 				if !reflect.DeepEqual(args, argsWithDefault) {
-					subCmd = cCtx.Command.Command(argsWithDefault.First())
+					subCmd = cmd.Command(argsWithDefault.First())
 				}
 			}
 		}
-	} else if cmd.parent == nil && cCtx.Command.DefaultCommand != "" {
-		if dc := cCtx.Command.Command(cCtx.Command.DefaultCommand); dc != cmd {
+	} else if cmd.parent == nil && cmd.DefaultCommand != "" {
+		tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
+
+		if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
 			subCmd = dc
 		}
 	}
 
 	if subCmd != nil {
-		/*
-			newcCtx := NewContext(cCtx.Command, nil, cCtx)
-			newcCtx.Command = cmd
-		*/
-		return subCmd.Run(ctx, cCtx.Args().Slice())
+		tracef("running sub-command %[1]q with arguments %[2]q (cmd=%[3]q)", subCmd.Name, cmd.Args(), cmd.Name)
+		return subCmd.Run(ctx, cmd.Args().Slice())
 	}
 
 	if cmd.Action == nil {
 		cmd.Action = helpCommandAction
 	}
 
-	if err := cmd.Action(cCtx); err != nil {
-		tracef("calling handleExitCoder with %[1]v", err)
-		deferErr = cCtx.Command.handleExitCoder(cCtx, err)
+	if err := cmd.Action(ctx, cmd); err != nil {
+		tracef("calling handleExitCoder with %[1]v (cmd=%[2]q)", err, cmd.Name)
+		deferErr = cmd.handleExitCoder(ctx, err)
 	}
 
-	tracef("returning deferErr")
+	tracef("returning deferErr (cmd=%[1]q)", cmd.Name)
 	return deferErr
 }
 
+func (cmd *Command) checkHelp() bool {
+	tracef("checking if help is wanted (cmd=%[1]q)", cmd.Name)
+
+	for _, name := range HelpFlag.Names() {
+		if cmd.Bool(name) {
+			return true
+		}
+	}
+
+	return false
+}
+
 func (cmd *Command) newFlagSet() (*flag.FlagSet, error) {
-	cmd.appliedFlags = append(cmd.appliedFlags, cmd.allFlags()...)
-	return flagSet(cmd.Name, cmd.allFlags())
+	allFlags := cmd.allFlags()
+
+	cmd.appliedFlags = append(cmd.appliedFlags, allFlags...)
+
+	tracef("making new flag set (cmd=%[1]q)", cmd.Name)
+
+	return newFlagSet(cmd.Name, allFlags)
 }
 
 func (cmd *Command) allFlags() []Flag {
@@ -516,8 +557,16 @@ func (cmd *Command) allFlags() []Flag {
 	return flags
 }
 
+// useShortOptionHandling traverses Lineage() for *any* ancestors
+// with UseShortOptionHandling
 func (cmd *Command) useShortOptionHandling() bool {
-	return cmd.UseShortOptionHandling
+	for _, pCmd := range cmd.Lineage() {
+		if pCmd.UseShortOptionHandling {
+			return true
+		}
+	}
+
+	return false
 }
 
 func (cmd *Command) suggestFlagFromError(err error, commandName string) (string, error) {
@@ -546,30 +595,47 @@ func (cmd *Command) suggestFlagFromError(err error, commandName string) (string,
 	return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + "\n\n", nil
 }
 
-func (cmd *Command) parseFlags(args Args, ctx *Context) (*flag.FlagSet, error) {
-	set, err := cmd.newFlagSet()
-	if err != nil {
-		return nil, err
+func (cmd *Command) parseFlags(args Args) (Args, error) {
+	tracef("parsing flags from arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
+
+	if v, err := cmd.newFlagSet(); err != nil {
+		return args, err
+	} else {
+		cmd.flagSet = v
 	}
 
 	if cmd.SkipFlagParsing {
-		return set, set.Parse(append([]string{"--"}, args.Tail()...))
+		tracef("skipping flag parsing (cmd=%[1]q)", cmd.Name)
+
+		return cmd.Args(), cmd.flagSet.Parse(append([]string{"--"}, args.Tail()...))
 	}
 
-	for pCtx := ctx.parent; pCtx != nil; pCtx = pCtx.parent {
-		if pCtx.Command == nil {
-			continue
-		}
+	tracef("walking command lineage for persistent flags (cmd=%[1]q)", cmd.Name)
+
+	for pCmd := cmd.parent; pCmd != nil; pCmd = pCmd.parent {
+		tracef(
+			"checking ancestor command=%[1]q for persistent flags (cmd=%[2]q)",
+			pCmd.Name, cmd.Name,
+		)
+
+		for _, fl := range pCmd.Flags {
+			flNames := fl.Names()
 
-		for _, fl := range pCtx.Command.Flags {
 			pfl, ok := fl.(PersistentFlag)
 			if !ok || !pfl.IsPersistent() {
+				tracef("skipping non-persistent flag %[1]q (cmd=%[2]q)", flNames, cmd.Name)
 				continue
 			}
 
+			tracef(
+				"checking for applying persistent flag=%[1]q pCmd=%[2]q (cmd=%[3]q)",
+				flNames, pCmd.Name, cmd.Name,
+			)
+
 			applyPersistentFlag := true
-			set.VisitAll(func(f *flag.Flag) {
-				for _, name := range fl.Names() {
+
+			cmd.flagSet.VisitAll(func(f *flag.Flag) {
+				for _, name := range flNames {
 					if name == f.Name {
 						applyPersistentFlag = false
 						break
@@ -578,26 +644,37 @@ func (cmd *Command) parseFlags(args Args, ctx *Context) (*flag.FlagSet, error) {
 			})
 
 			if !applyPersistentFlag {
+				tracef("not applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
+
 				continue
 			}
 
-			if err := fl.Apply(set); err != nil {
-				return nil, err
+			tracef("applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
+
+			if err := fl.Apply(cmd.flagSet); err != nil {
+				return cmd.Args(), err
 			}
 
+			tracef("appending to applied flags flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
 			cmd.appliedFlags = append(cmd.appliedFlags, fl)
 		}
 	}
 
-	if err := parseIter(set, cmd, args.Tail(), ctx.shellComplete); err != nil {
-		return nil, err
+	tracef("parsing flags iteratively tail=%[1]q (cmd=%[2]q)", args.Tail(), cmd.Name)
+
+	if err := parseIter(cmd.flagSet, cmd, args.Tail(), cmd.Root().EnableShellCompletion); err != nil {
+		return cmd.Args(), err
 	}
 
-	if err := normalizeFlags(cmd.Flags, set); err != nil {
-		return nil, err
+	tracef("normalizing flags (cmd=%[1]q)", cmd.Name)
+
+	if err := normalizeFlags(cmd.Flags, cmd.flagSet); err != nil {
+		return cmd.Args(), err
 	}
 
-	return set, nil
+	tracef("done parsing flags (cmd=%[1]q)", cmd.Name)
+
+	return cmd.Args(), nil
 }
 
 // Names returns the names including short names and aliases.
@@ -670,13 +747,13 @@ func (cmd *Command) appendCommand(aCmd *Command) {
 	}
 }
 
-func (cmd *Command) handleExitCoder(cCtx *Context, err error) error {
+func (cmd *Command) handleExitCoder(ctx context.Context, err error) error {
 	if cmd.parent != nil {
-		return cmd.parent.handleExitCoder(cCtx, err)
+		return cmd.parent.handleExitCoder(ctx, err)
 	}
 
 	if cmd.ExitErrHandler != nil {
-		cmd.ExitErrHandler(cCtx, err)
+		cmd.ExitErrHandler(ctx, cmd, err)
 		return err
 	}
 
@@ -687,9 +764,9 @@ func (cmd *Command) handleExitCoder(cCtx *Context, err error) error {
 func (cmd *Command) argsWithDefaultCommand(oldArgs Args) Args {
 	if cmd.DefaultCommand != "" {
 		rawArgs := append([]string{cmd.DefaultCommand}, oldArgs.Slice()...)
-		newArgs := args(rawArgs)
+		newArgs := &stringSliceArgs{v: rawArgs}
 
-		return &newArgs
+		return newArgs
 	}
 
 	return oldArgs
@@ -704,6 +781,222 @@ func (cmd *Command) Root() *Command {
 	return cmd.parent.Root()
 }
 
+func (cmd *Command) lookupFlag(name string) Flag {
+	for _, pCmd := range cmd.Lineage() {
+		for _, f := range pCmd.Flags {
+			for _, n := range f.Names() {
+				if n == name {
+					tracef("flag found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
+					return f
+				}
+			}
+		}
+	}
+
+	tracef("flag NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
+	return nil
+}
+
+func (cmd *Command) lookupFlagSet(name string) *flag.FlagSet {
+	for _, pCmd := range cmd.Lineage() {
+		if pCmd.flagSet == nil {
+			continue
+		}
+
+		if f := pCmd.flagSet.Lookup(name); f != nil {
+			tracef("matching flag set found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
+			return pCmd.flagSet
+		}
+	}
+
+	tracef("matching flag set NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
+	cmd.onInvalidFlag(context.TODO(), name)
+	return nil
+}
+
+func (cmd *Command) checkRequiredFlags() requiredFlagsErr {
+	tracef("checking for required flags (cmd=%[1]q)", cmd.Name)
+
+	missingFlags := []string{}
+
+	for _, f := range cmd.Flags {
+		if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
+			flagPresent := false
+			flagName := ""
+
+			for _, key := range f.Names() {
+				flagName = key
+
+				if cmd.IsSet(strings.TrimSpace(key)) {
+					flagPresent = true
+				}
+			}
+
+			if !flagPresent && flagName != "" {
+				missingFlags = append(missingFlags, flagName)
+			}
+		}
+	}
+
+	if len(missingFlags) != 0 {
+		tracef("found missing required flags %[1]q (cmd=%[2]q)", missingFlags, cmd.Name)
+
+		return &errRequiredFlags{missingFlags: missingFlags}
+	}
+
+	tracef("all required flags set (cmd=%[1]q)", cmd.Name)
+
+	return nil
+}
+
+func (cmd *Command) onInvalidFlag(ctx context.Context, name string) {
+	for cmd != nil {
+		if cmd.InvalidFlagAccessHandler != nil {
+			cmd.InvalidFlagAccessHandler(ctx, cmd, name)
+			break
+		}
+		cmd = cmd.parent
+	}
+}
+
+// NumFlags returns the number of flags set
+func (cmd *Command) NumFlags() int {
+	return cmd.flagSet.NFlag()
+}
+
+// Set sets a context flag to a value.
+func (cmd *Command) Set(name, value string) error {
+	if fs := cmd.lookupFlagSet(name); fs != nil {
+		return fs.Set(name, value)
+	}
+
+	return fmt.Errorf("no such flag -%s", name)
+}
+
+// IsSet determines if the flag was actually set
+func (cmd *Command) IsSet(name string) bool {
+	flSet := cmd.lookupFlagSet(name)
+
+	if flSet == nil {
+		return false
+	}
+
+	isSet := false
+
+	flSet.Visit(func(f *flag.Flag) {
+		if f.Name == name {
+			isSet = true
+		}
+	})
+
+	if isSet {
+		tracef("flag with name %[1]q found via flag set lookup (cmd=%[2]q)", name, cmd.Name)
+		return true
+	}
+
+	fl := cmd.lookupFlag(name)
+	if fl == nil {
+		tracef("flag with name %[1]q NOT found; assuming not set (cmd=%[2]q)", name, cmd.Name)
+		return false
+	}
+
+	isSet = fl.IsSet()
+	if isSet {
+		tracef("flag with name %[1]q is set (cmd=%[2]q)", name, cmd.Name)
+	} else {
+		tracef("flag with name %[1]q is NOT set (cmd=%[2]q)", name, cmd.Name)
+	}
+
+	return isSet
+}
+
+// LocalFlagNames returns a slice of flag names used in this
+// command.
+func (cmd *Command) LocalFlagNames() []string {
+	names := []string{}
+
+	cmd.flagSet.Visit(makeFlagNameVisitor(&names))
+
+	// Check the flags which have been set via env or file
+	if cmd.Flags != nil {
+		for _, f := range cmd.Flags {
+			if f.IsSet() {
+				names = append(names, f.Names()...)
+			}
+		}
+	}
+
+	// Sort out the duplicates since flag could be set via multiple
+	// paths
+	m := map[string]struct{}{}
+	uniqNames := []string{}
+
+	for _, name := range names {
+		if _, ok := m[name]; !ok {
+			m[name] = struct{}{}
+			uniqNames = append(uniqNames, name)
+		}
+	}
+
+	return uniqNames
+}
+
+// FlagNames returns a slice of flag names used by the this command
+// and all of its parent commands.
+func (cmd *Command) FlagNames() []string {
+	names := cmd.LocalFlagNames()
+
+	if cmd.parent != nil {
+		names = append(cmd.parent.FlagNames(), names...)
+	}
+
+	return names
+}
+
+// Lineage returns *this* command and all of its ancestor commands
+// in order from child to parent
+func (cmd *Command) Lineage() []*Command {
+	lineage := []*Command{cmd}
+
+	if cmd.parent != nil {
+		lineage = append(lineage, cmd.parent.Lineage()...)
+	}
+
+	return lineage
+}
+
+// Count returns the num of occurrences of this flag
+func (cmd *Command) Count(name string) int {
+	if fs := cmd.lookupFlagSet(name); fs != nil {
+		if cf, ok := fs.Lookup(name).Value.(Countable); ok {
+			return cf.Count()
+		}
+	}
+	return 0
+}
+
+// Value returns the value of the flag corresponding to `name`
+func (cmd *Command) Value(name string) interface{} {
+	if fs := cmd.lookupFlagSet(name); fs != nil {
+		tracef("value found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
+		return fs.Lookup(name).Value.(flag.Getter).Get()
+	}
+
+	tracef("value NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
+	return nil
+}
+
+// Args returns the command line arguments associated with the
+// command.
+func (cmd *Command) Args() Args {
+	return &stringSliceArgs{v: cmd.flagSet.Args()}
+}
+
+// NArg returns the number of the command line arguments.
+func (cmd *Command) NArg() int {
+	return cmd.Args().Len()
+}
+
 func hasCommand(commands []*Command, command *Command) bool {
 	for _, existing := range commands {
 		if command == existing {
@@ -714,12 +1007,12 @@ func hasCommand(commands []*Command, command *Command) bool {
 	return false
 }
 
-func runFlagActions(cCtx *Context, flags []Flag) error {
+func runFlagActions(ctx context.Context, cmd *Command, flags []Flag) error {
 	for _, fl := range flags {
 		isSet := false
 
 		for _, name := range fl.Names() {
-			if cCtx.IsSet(name) {
+			if cmd.IsSet(name) {
 				isSet = true
 				break
 			}
@@ -730,7 +1023,7 @@ func runFlagActions(cCtx *Context, flags []Flag) error {
 		}
 
 		if af, ok := fl.(ActionableFlag); ok {
-			if err := af.RunAction(cCtx); err != nil {
+			if err := af.RunAction(ctx, cmd); err != nil {
 				return err
 			}
 		}
@@ -750,3 +1043,21 @@ func checkStringSliceIncludes(want string, sSlice []string) bool {
 
 	return found
 }
+
+func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
+	return func(f *flag.Flag) {
+		nameParts := strings.Split(f.Name, ",")
+		name := strings.TrimSpace(nameParts[0])
+
+		for _, part := range nameParts {
+			part = strings.TrimSpace(part)
+			if len(part) > len(name) {
+				name = part
+			}
+		}
+
+		if name != "" {
+			*names = append(*names, name)
+		}
+	}
+}
diff --git a/command_test.go b/command_test.go
index bfa86b86d7..4c1a4e8631 100644
--- a/command_test.go
+++ b/command_test.go
@@ -10,6 +10,7 @@ import (
 	"net/mail"
 	"os"
 	"reflect"
+	"sort"
 	"strconv"
 	"strings"
 	"testing"
@@ -154,80 +155,80 @@ func TestCommandFlagParsing(t *testing.T) {
 		testArgs               []string
 		skipFlagParsing        bool
 		useShortOptionHandling bool
-		expectedErr            error
+		expectedErr            string
 	}{
 		// Test normal "not ignoring flags" flow
-		{testArgs: []string{"test-cmd", "-break", "blah", "blah"}, skipFlagParsing: false, useShortOptionHandling: false, expectedErr: errors.New("flag provided but not defined: -break")},
-		{testArgs: []string{"test-cmd", "blah", "blah"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil},   // Test SkipFlagParsing without any args that look like flags
-		{testArgs: []string{"test-cmd", "blah", "-break"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing with random flag arg
-		{testArgs: []string{"test-cmd", "blah", "-help"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil},  // Test SkipFlagParsing with "special" help flag arg
-		{testArgs: []string{"test-cmd", "blah", "-h"}, skipFlagParsing: false, useShortOptionHandling: true, expectedErr: nil},     // Test UseShortOptionHandling
+		{testArgs: []string{"test-cmd", "-break", "blah", "blah"}, skipFlagParsing: false, useShortOptionHandling: false, expectedErr: "flag provided but not defined: -break"},
+		{testArgs: []string{"test-cmd", "blah", "blah"}, skipFlagParsing: true, useShortOptionHandling: false},   // Test SkipFlagParsing without any args that look like flags
+		{testArgs: []string{"test-cmd", "blah", "-break"}, skipFlagParsing: true, useShortOptionHandling: false}, // Test SkipFlagParsing with random flag arg
+		{testArgs: []string{"test-cmd", "blah", "-help"}, skipFlagParsing: true, useShortOptionHandling: false},  // Test SkipFlagParsing with "special" help flag arg
+		{testArgs: []string{"test-cmd", "blah", "-h"}, skipFlagParsing: false, useShortOptionHandling: true},     // Test UseShortOptionHandling
 	}
 
 	for _, c := range cases {
 		t.Run(strings.Join(c.testArgs, " "), func(t *testing.T) {
-			cmd := &Command{Writer: io.Discard}
-			set := flag.NewFlagSet("test", 0)
-			_ = set.Parse(c.testArgs)
-
-			cCtx := NewContext(cmd, set, nil)
-
-			subCmd := &Command{
+			cmd := &Command{
+				Writer:          io.Discard,
 				Name:            "test-cmd",
 				Aliases:         []string{"tc"},
 				Usage:           "this is for testing",
 				Description:     "testing",
-				Action:          func(_ *Context) error { return nil },
+				Action:          func(context.Context, *Command) error { return nil },
 				SkipFlagParsing: c.skipFlagParsing,
 			}
 
-			ctx, cancel := context.WithTimeout(cCtx.Context, 100*time.Millisecond)
+			ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
 			t.Cleanup(cancel)
 
-			err := subCmd.Run(ctx, c.testArgs)
+			r := require.New(t)
 
-			expect(t, err, c.expectedErr)
-			// expect(t, cCtx.Args().Slice(), c.testArgs)
+			err := cmd.Run(ctx, c.testArgs)
+
+			if c.expectedErr != "" {
+				r.EqualError(err, c.expectedErr)
+			} else {
+				r.NoError(err)
+			}
 		})
 	}
 }
 
 func TestParseAndRunShortOpts(t *testing.T) {
 	testCases := []struct {
-		testArgs     args
+		testArgs     *stringSliceArgs
 		expectedErr  string
 		expectedArgs Args
 	}{
-		{testArgs: args{"test", "-a"}},
-		{testArgs: args{"test", "-c", "arg1", "arg2"}, expectedArgs: &args{"arg1", "arg2"}},
-		{testArgs: args{"test", "-f"}, expectedArgs: &args{}},
-		{testArgs: args{"test", "-ac", "--fgh"}, expectedArgs: &args{}},
-		{testArgs: args{"test", "-af"}, expectedArgs: &args{}},
-		{testArgs: args{"test", "-cf"}, expectedArgs: &args{}},
-		{testArgs: args{"test", "-acf"}, expectedArgs: &args{}},
-		{testArgs: args{"test", "--acf"}, expectedErr: "flag provided but not defined: -acf"},
-		{testArgs: args{"test", "-invalid"}, expectedErr: "flag provided but not defined: -invalid"},
-		{testArgs: args{"test", "-acf", "-invalid"}, expectedErr: "flag provided but not defined: -invalid"},
-		{testArgs: args{"test", "--invalid"}, expectedErr: "flag provided but not defined: -invalid"},
-		{testArgs: args{"test", "-acf", "--invalid"}, expectedErr: "flag provided but not defined: -invalid"},
-		{testArgs: args{"test", "-acf", "arg1", "-invalid"}, expectedArgs: &args{"arg1", "-invalid"}},
-		{testArgs: args{"test", "-acf", "arg1", "--invalid"}, expectedArgs: &args{"arg1", "--invalid"}},
-		{testArgs: args{"test", "-acfi", "not-arg", "arg1", "-invalid"}, expectedArgs: &args{"arg1", "-invalid"}},
-		{testArgs: args{"test", "-i", "ivalue"}, expectedArgs: &args{}},
-		{testArgs: args{"test", "-i", "ivalue", "arg1"}, expectedArgs: &args{"arg1"}},
-		{testArgs: args{"test", "-i"}, expectedErr: "flag needs an argument: -i"},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-a"}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-c", "arg1", "arg2"}}, expectedArgs: &stringSliceArgs{v: []string{"arg1", "arg2"}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-f"}}, expectedArgs: &stringSliceArgs{v: []string{}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-ac", "--fgh"}}, expectedArgs: &stringSliceArgs{v: []string{}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-af"}}, expectedArgs: &stringSliceArgs{v: []string{}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-cf"}}, expectedArgs: &stringSliceArgs{v: []string{}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-acf"}}, expectedArgs: &stringSliceArgs{v: []string{}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "--acf"}}, expectedErr: "flag provided but not defined: -acf"},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-invalid"}}, expectedErr: "flag provided but not defined: -invalid"},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-acf", "-invalid"}}, expectedErr: "flag provided but not defined: -invalid"},
+		{testArgs: &stringSliceArgs{v: []string{"test", "--invalid"}}, expectedErr: "flag provided but not defined: -invalid"},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-acf", "--invalid"}}, expectedErr: "flag provided but not defined: -invalid"},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-acf", "arg1", "-invalid"}}, expectedArgs: &stringSliceArgs{v: []string{"arg1", "-invalid"}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-acf", "arg1", "--invalid"}}, expectedArgs: &stringSliceArgs{v: []string{"arg1", "--invalid"}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-acfi", "not-arg", "arg1", "-invalid"}}, expectedArgs: &stringSliceArgs{v: []string{"arg1", "-invalid"}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-i", "ivalue"}}, expectedArgs: &stringSliceArgs{v: []string{}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-i", "ivalue", "arg1"}}, expectedArgs: &stringSliceArgs{v: []string{"arg1"}}},
+		{testArgs: &stringSliceArgs{v: []string{"test", "-i"}}, expectedErr: "flag needs an argument: -i"},
 	}
 
 	for _, tc := range testCases {
-		t.Run(strings.Join(tc.testArgs, " "), func(t *testing.T) {
+		t.Run(strings.Join(tc.testArgs.v, " "), func(t *testing.T) {
 			state := map[string]Args{"args": nil}
 
 			cmd := &Command{
 				Name:        "test",
 				Usage:       "this is for testing",
 				Description: "testing",
-				Action: func(c *Context) error {
-					state["args"] = c.Args()
+				Action: func(_ context.Context, cmd *Command) error {
+					state["args"] = cmd.Args()
 					return nil
 				},
 				UseShortOptionHandling: true,
@@ -240,7 +241,7 @@ func TestParseAndRunShortOpts(t *testing.T) {
 				},
 			}
 
-			err := cmd.Run(buildTestContext(t), tc.testArgs)
+			err := cmd.Run(buildTestContext(t), tc.testArgs.Slice())
 
 			r := require.New(t)
 
@@ -266,10 +267,10 @@ func TestParseAndRunShortOpts(t *testing.T) {
 func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
 	cmd := &Command{
 		Name: "bar",
-		Before: func(*Context) error {
+		Before: func(context.Context, *Command) error {
 			return fmt.Errorf("before error")
 		},
-		After: func(*Context) error {
+		After: func(context.Context, *Command) error {
 			return fmt.Errorf("after error")
 		},
 		Writer: io.Discard,
@@ -288,20 +289,20 @@ func TestCommand_Run_BeforeSavesMetadata(t *testing.T) {
 
 	cmd := &Command{
 		Name: "bar",
-		Before: func(c *Context) error {
-			c.Command.Metadata["msg"] = "hello world"
+		Before: func(_ context.Context, cmd *Command) error {
+			cmd.Metadata["msg"] = "hello world"
 			return nil
 		},
-		Action: func(c *Context) error {
-			msg, ok := c.Command.Metadata["msg"]
+		Action: func(_ context.Context, cmd *Command) error {
+			msg, ok := cmd.Metadata["msg"]
 			if !ok {
 				return errors.New("msg not found")
 			}
 			receivedMsgFromAction = msg.(string)
 			return nil
 		},
-		After: func(c *Context) error {
-			msg, ok := c.Command.Metadata["msg"]
+		After: func(_ context.Context, cmd *Command) error {
+			msg, ok := cmd.Metadata["msg"]
 			if !ok {
 				return errors.New("msg not found")
 			}
@@ -310,21 +311,11 @@ func TestCommand_Run_BeforeSavesMetadata(t *testing.T) {
 		},
 	}
 
-	err := cmd.Run(buildTestContext(t), []string{"foo", "bar"})
-	if err != nil {
-		t.Fatalf("expected no error from Run, got %s", err)
-	}
-
-	expectedMsg := "hello world"
+	r := require.New(t)
 
-	if receivedMsgFromAction != expectedMsg {
-		t.Fatalf("expected msg from Action to match. Given: %q\nExpected: %q",
-			receivedMsgFromAction, expectedMsg)
-	}
-	if receivedMsgFromAfter != expectedMsg {
-		t.Fatalf("expected msg from After to match. Given: %q\nExpected: %q",
-			receivedMsgFromAction, expectedMsg)
-	}
+	r.NoError(cmd.Run(buildTestContext(t), []string{"foo", "bar"}))
+	r.Equal("hello world", receivedMsgFromAction)
+	r.Equal("hello world", receivedMsgFromAfter)
 }
 
 func TestCommand_OnUsageError_hasCommandContext(t *testing.T) {
@@ -333,8 +324,8 @@ func TestCommand_OnUsageError_hasCommandContext(t *testing.T) {
 		Flags: []Flag{
 			&IntFlag{Name: "flag"},
 		},
-		OnUsageError: func(c *Context, err error, _ bool) error {
-			return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error())
+		OnUsageError: func(_ context.Context, cmd *Command, err error, _ bool) error {
+			return fmt.Errorf("intercepted in %s: %s", cmd.Name, err.Error())
 		},
 	}
 
@@ -354,7 +345,7 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
 		Flags: []Flag{
 			&IntFlag{Name: "flag"},
 		},
-		OnUsageError: func(_ *Context, err error, _ bool) error {
+		OnUsageError: func(_ context.Context, _ *Command, err error, _ bool) error {
 			if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
 				t.Errorf("Expect an invalid value error, but got \"%v\"", err)
 			}
@@ -383,7 +374,7 @@ func TestCommand_OnUsageError_WithSubcommand(t *testing.T) {
 		Flags: []Flag{
 			&IntFlag{Name: "flag"},
 		},
-		OnUsageError: func(_ *Context, err error, _ bool) error {
+		OnUsageError: func(_ context.Context, _ *Command, err error, _ bool) error {
 			if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
 				t.Errorf("Expect an invalid value error, but got \"%v\"", err)
 			}
@@ -403,8 +394,8 @@ func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
 			{
 				Name:  "baz",
 				Usage: "this is for testing",
-				Action: func(cCtx *Context) error {
-					require.Equal(t, io.Discard, cCtx.Command.Root().ErrWriter)
+				Action: func(_ context.Context, cmd *Command) error {
+					require.Equal(t, io.Discard, cmd.Root().ErrWriter)
 
 					return nil
 				},
@@ -417,49 +408,51 @@ func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
 
 func TestCommandSkipFlagParsing(t *testing.T) {
 	cases := []struct {
-		testArgs     args
-		expectedArgs *args
+		testArgs     *stringSliceArgs
+		expectedArgs *stringSliceArgs
 		expectedErr  error
 	}{
-		{testArgs: args{"some-command", "some-arg", "--flag", "foo"}, expectedArgs: &args{"some-arg", "--flag", "foo"}, expectedErr: nil},
-		{testArgs: args{"some-command", "some-arg", "--flag=foo"}, expectedArgs: &args{"some-arg", "--flag=foo"}, expectedErr: nil},
+		{testArgs: &stringSliceArgs{v: []string{"some-command", "some-arg", "--flag", "foo"}}, expectedArgs: &stringSliceArgs{v: []string{"some-arg", "--flag", "foo"}}, expectedErr: nil},
+		{testArgs: &stringSliceArgs{v: []string{"some-command", "some-arg", "--flag=foo"}}, expectedArgs: &stringSliceArgs{v: []string{"some-arg", "--flag=foo"}}, expectedErr: nil},
 	}
 
 	for _, c := range cases {
-		var args Args
-		cmd := &Command{
-			SkipFlagParsing: true,
-			Name:            "some-command",
-			Flags: []Flag{
-				&StringFlag{Name: "flag"},
-			},
-			Action: func(c *Context) error {
-				args = c.Args()
-				return nil
-			},
-			Writer: io.Discard,
-		}
+		t.Run(strings.Join(c.testArgs.Slice(), " "), func(t *testing.T) {
+			var args Args
+			cmd := &Command{
+				SkipFlagParsing: true,
+				Name:            "some-command",
+				Flags: []Flag{
+					&StringFlag{Name: "flag"},
+				},
+				Action: func(_ context.Context, cmd *Command) error {
+					args = cmd.Args()
+					return nil
+				},
+				Writer: io.Discard,
+			}
 
-		err := cmd.Run(buildTestContext(t), c.testArgs)
-		expect(t, err, c.expectedErr)
-		expect(t, args, c.expectedArgs)
+			err := cmd.Run(buildTestContext(t), c.testArgs.Slice())
+			expect(t, err, c.expectedErr)
+			expect(t, args, c.expectedArgs)
+		})
 	}
 }
 
 func TestCommand_Run_CustomShellCompleteAcceptsMalformedFlags(t *testing.T) {
 	cases := []struct {
-		testArgs    args
+		testArgs    *stringSliceArgs
 		expectedOut string
 	}{
-		{testArgs: args{"--undefined"}, expectedOut: "found 0 args"},
-		{testArgs: args{"--number"}, expectedOut: "found 0 args"},
-		{testArgs: args{"--number", "forty-two"}, expectedOut: "found 0 args"},
-		{testArgs: args{"--number", "42"}, expectedOut: "found 0 args"},
-		{testArgs: args{"--number", "42", "newArg"}, expectedOut: "found 1 args"},
+		{testArgs: &stringSliceArgs{v: []string{"--undefined"}}, expectedOut: "found 0 args"},
+		{testArgs: &stringSliceArgs{v: []string{"--number"}}, expectedOut: "found 0 args"},
+		{testArgs: &stringSliceArgs{v: []string{"--number", "forty-two"}}, expectedOut: "found 0 args"},
+		{testArgs: &stringSliceArgs{v: []string{"--number", "42"}}, expectedOut: "found 0 args"},
+		{testArgs: &stringSliceArgs{v: []string{"--number", "42", "newArg"}}, expectedOut: "found 1 args"},
 	}
 
 	for _, c := range cases {
-		t.Run(strings.Join(c.testArgs, " "), func(t *testing.T) {
+		t.Run(strings.Join(c.testArgs.Slice(), " "), func(t *testing.T) {
 			out := &bytes.Buffer{}
 			cmd := &Command{
 				Writer:                out,
@@ -472,18 +465,18 @@ func TestCommand_Run_CustomShellCompleteAcceptsMalformedFlags(t *testing.T) {
 						Usage: "A number to parse",
 					},
 				},
-				ShellComplete: func(cCtx *Context) {
-					fmt.Fprintf(cCtx.Command.Root().Writer, "found %[1]d args", cCtx.NArg())
+				ShellComplete: func(_ context.Context, cmd *Command) {
+					fmt.Fprintf(cmd.Root().Writer, "found %[1]d args", cmd.NArg())
 				},
 			}
 
-			osArgs := args{"bar"}
-			osArgs = append(osArgs, c.testArgs...)
-			osArgs = append(osArgs, "--generate-shell-completion")
+			osArgs := &stringSliceArgs{v: []string{"bar"}}
+			osArgs.v = append(osArgs.v, c.testArgs.Slice()...)
+			osArgs.v = append(osArgs.v, "--generate-shell-completion")
 
 			r := require.New(t)
 
-			r.NoError(cmd.Run(buildTestContext(t), osArgs))
+			r.NoError(cmd.Run(buildTestContext(t), osArgs.Slice()))
 			r.Equal(c.expectedOut, out.String())
 		})
 	}
@@ -576,7 +569,7 @@ func TestCommand_RunSubcommandWithDefault(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "foo",
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return errors.New("should not run this subcommand")
 				},
 			},
@@ -584,7 +577,7 @@ func TestCommand_RunSubcommandWithDefault(t *testing.T) {
 				Name:     "bar",
 				Usage:    "this is for testing",
 				Commands: []*Command{{}}, // some subcommand
-				Action: func(*Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
@@ -602,8 +595,8 @@ func TestCommand_Run(t *testing.T) {
 	s := ""
 
 	cmd := &Command{
-		Action: func(c *Context) error {
-			s = s + c.Args().First()
+		Action: func(_ context.Context, cmd *Command) error {
+			s = s + cmd.Args().First()
 			return nil
 		},
 	}
@@ -879,35 +872,6 @@ func TestCommand_Setup_defaultsWriter(t *testing.T) {
 	expect(t, cmd.Writer, os.Stdout)
 }
 
-func TestCommand_RunAsSubcommandParseFlags(t *testing.T) {
-	var cCtx *Context
-
-	cmd := &Command{
-		Commands: []*Command{
-			{
-				Name: "foo",
-				Action: func(c *Context) error {
-					cCtx = c
-					return nil
-				},
-				Flags: []Flag{
-					&StringFlag{
-						Name:  "lang",
-						Value: "english",
-						Usage: "language for the greeting",
-					},
-				},
-				Before: func(_ *Context) error { return nil },
-			},
-		},
-	}
-
-	_ = cmd.Run(buildTestContext(t), []string{"", "foo", "--lang", "spanish", "abcd"})
-
-	expect(t, cCtx.Args().Get(0), "abcd")
-	expect(t, cCtx.String("lang"), "spanish")
-}
-
 func TestCommand_CommandWithFlagBeforeTerminator(t *testing.T) {
 	var parsedOption string
 	var args Args
@@ -919,21 +883,23 @@ func TestCommand_CommandWithFlagBeforeTerminator(t *testing.T) {
 				Flags: []Flag{
 					&StringFlag{Name: "option", Value: "", Usage: "some option"},
 				},
-				Action: func(c *Context) error {
-					parsedOption = c.String("option")
-					args = c.Args()
+				Action: func(_ context.Context, cmd *Command) error {
+					parsedOption = cmd.String("option")
+					args = cmd.Args()
 					return nil
 				},
 			},
 		},
 	}
 
-	_ = cmd.Run(buildTestContext(t), []string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"})
+	r := require.New(t)
 
-	expect(t, parsedOption, "my-option")
-	expect(t, args.Get(0), "my-arg")
-	expect(t, args.Get(1), "--")
-	expect(t, args.Get(2), "--notARealFlag")
+	r.NoError(cmd.Run(buildTestContext(t), []string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}))
+
+	r.Equal("my-option", parsedOption)
+	r.Equal("my-arg", args.Get(0))
+	r.Equal("--", args.Get(1))
+	r.Equal("--notARealFlag", args.Get(2))
 }
 
 func TestCommand_CommandWithDash(t *testing.T) {
@@ -943,18 +909,20 @@ func TestCommand_CommandWithDash(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "cmd",
-				Action: func(c *Context) error {
-					args = c.Args()
+				Action: func(_ context.Context, cmd *Command) error {
+					args = cmd.Args()
 					return nil
 				},
 			},
 		},
 	}
 
-	_ = cmd.Run(buildTestContext(t), []string{"", "cmd", "my-arg", "-"})
+	r := require.New(t)
 
-	expect(t, args.Get(0), "my-arg")
-	expect(t, args.Get(1), "-")
+	r.NoError(cmd.Run(buildTestContext(t), []string{"", "cmd", "my-arg", "-"}))
+	r.NotNil(args)
+	r.Equal("my-arg", args.Get(0))
+	r.Equal("-", args.Get(1))
 }
 
 func TestCommand_CommandWithNoFlagBeforeTerminator(t *testing.T) {
@@ -964,19 +932,22 @@ func TestCommand_CommandWithNoFlagBeforeTerminator(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "cmd",
-				Action: func(c *Context) error {
-					args = c.Args()
+				Action: func(_ context.Context, cmd *Command) error {
+					args = cmd.Args()
 					return nil
 				},
 			},
 		},
 	}
 
-	_ = cmd.Run(buildTestContext(t), []string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
+	r := require.New(t)
 
-	expect(t, args.Get(0), "my-arg")
-	expect(t, args.Get(1), "--")
-	expect(t, args.Get(2), "notAFlagAtAll")
+	r.NoError(cmd.Run(buildTestContext(t), []string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}))
+
+	r.NotNil(args)
+	r.Equal("my-arg", args.Get(0))
+	r.Equal("--", args.Get(1))
+	r.Equal("notAFlagAtAll", args.Get(2))
 }
 
 func TestCommand_SkipFlagParsing(t *testing.T) {
@@ -984,8 +955,8 @@ func TestCommand_SkipFlagParsing(t *testing.T) {
 
 	cmd := &Command{
 		SkipFlagParsing: true,
-		Action: func(c *Context) error {
-			args = c.Args()
+		Action: func(_ context.Context, cmd *Command) error {
+			args = cmd.Args()
 			return nil
 		},
 	}
@@ -1002,12 +973,12 @@ func TestCommand_VisibleCommands(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name:   "frob",
-				Action: func(_ *Context) error { return nil },
+				Action: func(context.Context, *Command) error { return nil },
 			},
 			{
 				Name:   "frib",
 				Hidden: true,
-				Action: func(_ *Context) error { return nil },
+				Action: func(context.Context, *Command) error { return nil },
 			},
 		},
 	}
@@ -1058,10 +1029,10 @@ func TestCommand_UseShortOptionHandling(t *testing.T) {
 		&BoolFlag{Name: "two", Aliases: []string{"t"}},
 		&StringFlag{Name: "name", Aliases: []string{"n"}},
 	}
-	cmd.Action = func(c *Context) error {
-		one = c.Bool("one")
-		two = c.Bool("two")
-		name = c.String("name")
+	cmd.Action = func(_ context.Context, cmd *Command) error {
+		one = cmd.Bool("one")
+		two = cmd.Bool("two")
+		name = cmd.String("name")
 		return nil
 	}
 
@@ -1096,10 +1067,10 @@ func TestCommand_UseShortOptionHandlingCommand(t *testing.T) {
 			&BoolFlag{Name: "two", Aliases: []string{"t"}},
 			&StringFlag{Name: "name", Aliases: []string{"n"}},
 		},
-		Action: func(c *Context) error {
-			one = c.Bool("one")
-			two = c.Bool("two")
-			name = c.String("name")
+		Action: func(_ context.Context, cmd *Command) error {
+			one = cmd.Bool("one")
+			two = cmd.Bool("two")
+			name = cmd.String("name")
 			return nil
 		},
 		UseShortOptionHandling: true,
@@ -1134,35 +1105,39 @@ func TestCommand_UseShortOptionHandlingCommand_missing_value(t *testing.T) {
 func TestCommand_UseShortOptionHandlingSubCommand(t *testing.T) {
 	var one, two bool
 	var name string
-	expected := "expectedName"
 
 	cmd := buildMinimalTestCommand()
 	cmd.UseShortOptionHandling = true
-	command := &Command{
-		Name: "cmd",
-	}
-	subCommand := &Command{
-		Name: "sub",
-		Flags: []Flag{
-			&BoolFlag{Name: "one", Aliases: []string{"o"}},
-			&BoolFlag{Name: "two", Aliases: []string{"t"}},
-			&StringFlag{Name: "name", Aliases: []string{"n"}},
-		},
-		Action: func(c *Context) error {
-			one = c.Bool("one")
-			two = c.Bool("two")
-			name = c.String("name")
-			return nil
+	cmd.Commands = []*Command{
+		{
+			Name: "cmd",
+			Commands: []*Command{
+				{
+					Name: "sub",
+					Flags: []Flag{
+						&BoolFlag{Name: "one", Aliases: []string{"o"}},
+						&BoolFlag{Name: "two", Aliases: []string{"t"}},
+						&StringFlag{Name: "name", Aliases: []string{"n"}},
+					},
+					Action: func(_ context.Context, cmd *Command) error {
+						one = cmd.Bool("one")
+						two = cmd.Bool("two")
+						name = cmd.String("name")
+						return nil
+					},
+				},
+			},
 		},
 	}
-	command.Commands = []*Command{subCommand}
-	cmd.Commands = []*Command{command}
 
-	err := cmd.Run(buildTestContext(t), []string{"", "cmd", "sub", "-on", expected})
-	expect(t, err, nil)
-	expect(t, one, true)
-	expect(t, two, false)
-	expect(t, name, expected)
+	r := require.New(t)
+
+	expected := "expectedName"
+
+	r.NoError(cmd.Run(buildTestContext(t), []string{"", "cmd", "sub", "-on", expected}))
+	r.True(one)
+	r.False(two)
+	r.Equal(expected, name)
 }
 
 func TestCommand_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) {
@@ -1199,11 +1174,11 @@ func TestCommand_UseShortOptionAfterSliceFlag(t *testing.T) {
 		&BoolFlag{Name: "two", Aliases: []string{"t"}},
 		&StringFlag{Name: "name", Aliases: []string{"n"}},
 	}
-	cmd.Action = func(c *Context) error {
-		sliceVal = c.StringSlice("env")
-		one = c.Bool("one")
-		two = c.Bool("two")
-		name = c.String("name")
+	cmd.Action = func(_ context.Context, cmd *Command) error {
+		sliceVal = cmd.StringSlice("env")
+		one = cmd.Bool("one")
+		two = cmd.Bool("two")
+		name = cmd.String("name")
 		return nil
 	}
 
@@ -1222,8 +1197,8 @@ func TestCommand_Float64Flag(t *testing.T) {
 		Flags: []Flag{
 			&FloatFlag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
 		},
-		Action: func(c *Context) error {
-			meters = c.Float("height")
+		Action: func(_ context.Context, cmd *Command) error {
+			meters = cmd.Float("height")
 			return nil
 		},
 	}
@@ -1244,9 +1219,9 @@ func TestCommand_ParseSliceFlags(t *testing.T) {
 					&IntSliceFlag{Name: "p", Value: []int64{}, Usage: "set one or more ip addr"},
 					&StringSliceFlag{Name: "ip", Value: []string{}, Usage: "set one or more ports to open"},
 				},
-				Action: func(c *Context) error {
-					parsedIntSlice = c.IntSlice("p")
-					parsedStringSlice = c.StringSlice("ip")
+				Action: func(_ context.Context, cmd *Command) error {
+					parsedIntSlice = cmd.IntSlice("p")
+					parsedStringSlice = cmd.StringSlice("ip")
 					return nil
 				},
 			},
@@ -1272,9 +1247,9 @@ func TestCommand_ParseSliceFlagsWithMissingValue(t *testing.T) {
 					&IntSliceFlag{Name: "a", Usage: "set numbers"},
 					&StringSliceFlag{Name: "str", Usage: "set strings"},
 				},
-				Action: func(c *Context) error {
-					parsedIntSlice = c.IntSlice("a")
-					parsedStringSlice = c.StringSlice("str")
+				Action: func(_ context.Context, cmd *Command) error {
+					parsedIntSlice = cmd.IntSlice("a")
+					parsedStringSlice = cmd.StringSlice("str")
 					return nil
 				},
 			},
@@ -1312,8 +1287,8 @@ func TestCommand_SetStdin(t *testing.T) {
 	cmd := &Command{
 		Name:   "test",
 		Reader: strings.NewReader("Hello World!"),
-		Action: func(c *Context) error {
-			_, err := c.Command.Reader.Read(buf)
+		Action: func(_ context.Context, cmd *Command) error {
+			_, err := cmd.Reader.Read(buf)
 			return err
 		},
 	}
@@ -1340,8 +1315,8 @@ func TestCommand_SetStdin_Subcommand(t *testing.T) {
 				Commands: []*Command{
 					{
 						Name: "subcommand",
-						Action: func(c *Context) error {
-							_, err := c.Command.Root().Reader.Read(buf)
+						Action: func(_ context.Context, cmd *Command) error {
+							_, err := cmd.Root().Reader.Read(buf)
 							return err
 						},
 					},
@@ -1384,10 +1359,10 @@ func TestCommand_BeforeFunc(t *testing.T) {
 	var err error
 
 	cmd := &Command{
-		Before: func(c *Context) error {
+		Before: func(_ context.Context, cmd *Command) error {
 			counts.Total++
 			counts.Before = counts.Total
-			s := c.String("opt")
+			s := cmd.String("opt")
 			if s == "fail" {
 				return beforeError
 			}
@@ -1397,7 +1372,7 @@ func TestCommand_BeforeFunc(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "sub",
-				Action: func(*Context) error {
+				Action: func(context.Context, *Command) error {
 					counts.Total++
 					counts.SubCommand = counts.Total
 					return nil
@@ -1448,7 +1423,7 @@ func TestCommand_BeforeFunc(t *testing.T) {
 	counts = &opCounts{}
 
 	afterError := errors.New("fail again")
-	cmd.After = func(_ *Context) error {
+	cmd.After = func(context.Context, *Command) error {
 		return afterError
 	}
 
@@ -1476,12 +1451,12 @@ func TestCommand_BeforeAfterFuncShellCompletion(t *testing.T) {
 
 	cmd := &Command{
 		EnableShellCompletion: true,
-		Before: func(*Context) error {
+		Before: func(context.Context, *Command) error {
 			counts.Total++
 			counts.Before = counts.Total
 			return nil
 		},
-		After: func(*Context) error {
+		After: func(context.Context, *Command) error {
 			counts.Total++
 			counts.After = counts.Total
 			return nil
@@ -1489,7 +1464,7 @@ func TestCommand_BeforeAfterFuncShellCompletion(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "sub",
-				Action: func(*Context) error {
+				Action: func(context.Context, *Command) error {
 					counts.Total++
 					counts.SubCommand = counts.Total
 					return nil
@@ -1527,10 +1502,10 @@ func TestCommand_AfterFunc(t *testing.T) {
 	var err error
 
 	cmd := &Command{
-		After: func(c *Context) error {
+		After: func(_ context.Context, cmd *Command) error {
 			counts.Total++
 			counts.After = counts.Total
-			s := c.String("opt")
+			s := cmd.String("opt")
 			if s == "fail" {
 				return afterError
 			}
@@ -1540,7 +1515,7 @@ func TestCommand_AfterFunc(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "sub",
-				Action: func(*Context) error {
+				Action: func(context.Context, *Command) error {
 					counts.Total++
 					counts.SubCommand = counts.Total
 					return nil
@@ -1740,7 +1715,7 @@ func TestRequiredFlagCommandRunBehavior(t *testing.T) {
 				Commands: []*Command{{
 					Name:  "mySubCommand",
 					Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
-					Action: func(c *Context) error {
+					Action: func(context.Context, *Command) error {
 						return nil
 					},
 				}},
@@ -1798,13 +1773,12 @@ func TestCommand_VersionPrinter(t *testing.T) {
 	}()
 
 	wasCalled := false
-	VersionPrinter = func(*Context) {
+	VersionPrinter = func(*Command) {
 		wasCalled = true
 	}
 
 	cmd := &Command{}
-	cCtx := NewContext(cmd, nil, nil)
-	ShowVersion(cCtx)
+	ShowVersion(cmd)
 
 	if wasCalled == false {
 		t.Errorf("Version printer expected to be called, but was not")
@@ -1814,14 +1788,14 @@ func TestCommand_VersionPrinter(t *testing.T) {
 func TestCommand_CommandNotFound(t *testing.T) {
 	counts := &opCounts{}
 	cmd := &Command{
-		CommandNotFound: func(*Context, string) {
+		CommandNotFound: func(context.Context, *Command, string) {
 			counts.Total++
 			counts.CommandNotFound = counts.Total
 		},
 		Commands: []*Command{
 			{
 				Name: "bar",
-				Action: func(*Context) error {
+				Action: func(context.Context, *Command) error {
 					counts.Total++
 					counts.SubCommand = counts.Total
 					return nil
@@ -1838,145 +1812,163 @@ func TestCommand_CommandNotFound(t *testing.T) {
 }
 
 func TestCommand_OrderOfOperations(t *testing.T) {
-	counts := &opCounts{}
+	buildCmdCounts := func() (*Command, *opCounts) {
+		counts := &opCounts{}
 
-	resetCounts := func() { counts = &opCounts{} }
+		cmd := &Command{
+			EnableShellCompletion: true,
+			ShellComplete: func(context.Context, *Command) {
+				counts.Total++
+				counts.ShellComplete = counts.Total
+			},
+			OnUsageError: func(context.Context, *Command, error, bool) error {
+				counts.Total++
+				counts.OnUsageError = counts.Total
+				return errors.New("hay OnUsageError")
+			},
+			Writer: io.Discard,
+		}
 
-	cmd := &Command{
-		EnableShellCompletion: true,
-		ShellComplete: func(*Context) {
+		beforeNoError := func(context.Context, *Command) error {
 			counts.Total++
-			counts.ShellComplete = counts.Total
-		},
-		OnUsageError: func(*Context, error, bool) error {
+			counts.Before = counts.Total
+			return nil
+		}
+
+		cmd.Before = beforeNoError
+		cmd.CommandNotFound = func(context.Context, *Command, string) {
 			counts.Total++
-			counts.OnUsageError = counts.Total
-			return errors.New("hay OnUsageError")
-		},
-		Writer: io.Discard,
-	}
+			counts.CommandNotFound = counts.Total
+		}
 
-	beforeNoError := func(*Context) error {
-		counts.Total++
-		counts.Before = counts.Total
-		return nil
-	}
+		afterNoError := func(context.Context, *Command) error {
+			counts.Total++
+			counts.After = counts.Total
+			return nil
+		}
 
-	beforeError := func(*Context) error {
-		counts.Total++
-		counts.Before = counts.Total
-		return errors.New("hay Before")
-	}
+		cmd.After = afterNoError
+		cmd.Commands = []*Command{
+			{
+				Name: "bar",
+				Action: func(context.Context, *Command) error {
+					counts.Total++
+					counts.SubCommand = counts.Total
+					return nil
+				},
+			},
+		}
 
-	cmd.Before = beforeNoError
-	cmd.CommandNotFound = func(*Context, string) {
-		counts.Total++
-		counts.CommandNotFound = counts.Total
-	}
+		cmd.Action = func(context.Context, *Command) error {
+			counts.Total++
+			counts.Action = counts.Total
+			return nil
+		}
 
-	afterNoError := func(*Context) error {
-		counts.Total++
-		counts.After = counts.Total
-		return nil
+		return cmd, counts
 	}
 
-	afterError := func(*Context) error {
-		counts.Total++
-		counts.After = counts.Total
-		return errors.New("hay After")
-	}
+	t.Run("on usage error", func(t *testing.T) {
+		cmd, counts := buildCmdCounts()
+		r := require.New(t)
 
-	cmd.After = afterNoError
-	cmd.Commands = []*Command{
-		{
-			Name: "bar",
-			Action: func(*Context) error {
-				counts.Total++
-				counts.SubCommand = counts.Total
-				return nil
-			},
-		},
-	}
+		_ = cmd.Run(buildTestContext(t), []string{"command", "--nope"})
+		r.Equal(1, counts.OnUsageError)
+		r.Equal(1, counts.Total)
+	})
 
-	cmd.Action = func(*Context) error {
-		counts.Total++
-		counts.Action = counts.Total
-		return nil
-	}
+	t.Run("shell complete", func(t *testing.T) {
+		cmd, counts := buildCmdCounts()
+		r := require.New(t)
 
-	_ = cmd.Run(buildTestContext(t), []string{"command", "--nope"})
-	expect(t, counts.OnUsageError, 1)
-	expect(t, counts.Total, 1)
+		_ = cmd.Run(buildTestContext(t), []string{"command", "--" + "generate-shell-completion"})
+		r.Equal(1, counts.ShellComplete)
+		r.Equal(1, counts.Total)
+	})
 
-	resetCounts()
+	t.Run("nil on usage error", func(t *testing.T) {
+		cmd, counts := buildCmdCounts()
+		cmd.OnUsageError = nil
 
-	_ = cmd.Run(buildTestContext(t), []string{"command", fmt.Sprintf("--%s", "generate-shell-completion")})
-	expect(t, counts.ShellComplete, 1)
-	expect(t, counts.Total, 1)
+		_ = cmd.Run(buildTestContext(t), []string{"command", "--nope"})
+		require.Equal(t, 0, counts.Total)
+	})
 
-	resetCounts()
+	t.Run("before after action hooks", func(t *testing.T) {
+		cmd, counts := buildCmdCounts()
+		r := require.New(t)
+
+		_ = cmd.Run(buildTestContext(t), []string{"command", "foo"})
+		r.Equal(0, counts.OnUsageError)
+		r.Equal(1, counts.Before)
+		r.Equal(0, counts.CommandNotFound)
+		r.Equal(2, counts.Action)
+		r.Equal(3, counts.After)
+		r.Equal(3, counts.Total)
+	})
 
-	oldOnUsageError := cmd.OnUsageError
-	cmd.OnUsageError = nil
-	_ = cmd.Run(buildTestContext(t), []string{"command", "--nope"})
-	expect(t, counts.Total, 0)
-	cmd.OnUsageError = oldOnUsageError
+	t.Run("before with error", func(t *testing.T) {
+		cmd, counts := buildCmdCounts()
+		cmd.Before = func(context.Context, *Command) error {
+			counts.Total++
+			counts.Before = counts.Total
+			return errors.New("hay Before")
+		}
 
-	resetCounts()
+		r := require.New(t)
 
-	_ = cmd.Run(buildTestContext(t), []string{"command", "foo"})
-	expect(t, counts.OnUsageError, 0)
-	expect(t, counts.Before, 1)
-	expect(t, counts.CommandNotFound, 0)
-	expect(t, counts.Action, 2)
-	expect(t, counts.After, 3)
-	expect(t, counts.Total, 3)
-
-	resetCounts()
-
-	cmd.Before = beforeError
-	_ = cmd.Run(buildTestContext(t), []string{"command", "bar"})
-	expect(t, counts.OnUsageError, 0)
-	expect(t, counts.Before, 1)
-	expect(t, counts.After, 2)
-	expect(t, counts.Total, 2)
-	cmd.Before = beforeNoError
-
-	resetCounts()
-
-	cmd.After = nil
-	_ = cmd.Run(buildTestContext(t), []string{"command", "bar"})
-	expect(t, counts.OnUsageError, 0)
-	expect(t, counts.Before, 1)
-	expect(t, counts.SubCommand, 2)
-	expect(t, counts.Total, 2)
-	cmd.After = afterNoError
-
-	resetCounts()
-
-	cmd.After = afterError
-	err := cmd.Run(buildTestContext(t), []string{"command", "bar"})
-	if err == nil {
-		t.Fatalf("expected a non-nil error")
-	}
-	expect(t, counts.OnUsageError, 0)
-	expect(t, counts.Before, 1)
-	expect(t, counts.SubCommand, 2)
-	expect(t, counts.After, 3)
-	expect(t, counts.Total, 3)
-	cmd.After = afterNoError
+		_ = cmd.Run(buildTestContext(t), []string{"command", "bar"})
+		r.Equal(0, counts.OnUsageError)
+		r.Equal(1, counts.Before)
+		r.Equal(2, counts.After)
+		r.Equal(2, counts.Total)
+	})
 
-	resetCounts()
+	t.Run("nil after", func(t *testing.T) {
+		cmd, counts := buildCmdCounts()
+		cmd.After = nil
+		r := require.New(t)
+
+		_ = cmd.Run(buildTestContext(t), []string{"command", "bar"})
+		r.Equal(0, counts.OnUsageError)
+		r.Equal(1, counts.Before)
+		r.Equal(2, counts.SubCommand)
+		r.Equal(2, counts.Total)
+	})
 
-	oldCommands := cmd.Commands
-	cmd.Commands = nil
-	_ = cmd.Run(buildTestContext(t), []string{"command"})
-	expect(t, counts.OnUsageError, 0)
-	expect(t, counts.Before, 1)
-	expect(t, counts.Action, 2)
-	expect(t, counts.After, 3)
-	expect(t, counts.Total, 3)
-	cmd.Commands = oldCommands
+	t.Run("after errors", func(t *testing.T) {
+		cmd, counts := buildCmdCounts()
+		cmd.After = func(context.Context, *Command) error {
+			counts.Total++
+			counts.After = counts.Total
+			return errors.New("hay After")
+		}
+
+		r := require.New(t)
+
+		err := cmd.Run(buildTestContext(t), []string{"command", "bar"})
+		if err == nil {
+			t.Fatalf("expected a non-nil error")
+		}
+		r.Equal(0, counts.OnUsageError)
+		r.Equal(1, counts.Before)
+		r.Equal(2, counts.SubCommand)
+		r.Equal(3, counts.After)
+		r.Equal(3, counts.Total)
+	})
+
+	t.Run("nil commands", func(t *testing.T) {
+		cmd, counts := buildCmdCounts()
+		cmd.Commands = nil
+		r := require.New(t)
+
+		_ = cmd.Run(buildTestContext(t), []string{"command"})
+		r.Equal(0, counts.OnUsageError)
+		r.Equal(1, counts.Before)
+		r.Equal(2, counts.Action)
+		r.Equal(3, counts.After)
+		r.Equal(3, counts.Total)
+	})
 }
 
 func TestCommand_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
@@ -2002,7 +1994,7 @@ func TestCommand_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
 				Name:        "foo",
 				Description: "descriptive wall of text about how it does foo things",
 				Commands:    []*Command{subCmdBar, subCmdBaz},
-				Action:      func(c *Context) error { return nil },
+				Action:      func(context.Context, *Command) error { return nil },
 				Writer:      buf,
 			}
 
@@ -2102,7 +2094,7 @@ func TestCommand_Run_Help(t *testing.T) {
 				Usage:    "make an explosive entrance",
 				Writer:   buf,
 				HideHelp: tt.hideHelp,
-				Action: func(*Context) error {
+				Action: func(context.Context, *Command) error {
 					buf.WriteString("boom I say!")
 					return nil
 				},
@@ -2134,7 +2126,7 @@ func TestCommand_Run_Version(t *testing.T) {
 				Usage:   "make an explosive entrance",
 				Version: "0.1.0",
 				Writer:  buf,
-				Action: func(*Context) error {
+				Action: func(context.Context, *Command) error {
 					buf.WriteString("boom I say!")
 					return nil
 				},
@@ -2314,8 +2306,8 @@ func TestCommand_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) {
 					},
 				},
 				Name:   "bar",
-				Before: func(c *Context) error { return fmt.Errorf("before error") },
-				After:  func(c *Context) error { return fmt.Errorf("after error") },
+				Before: func(context.Context, *Command) error { return fmt.Errorf("before error") },
+				After:  func(context.Context, *Command) error { return fmt.Errorf("after error") },
 			},
 		},
 	}
@@ -2338,7 +2330,7 @@ func TestCommand_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
 		Flags: []Flag{
 			&IntFlag{Name: "flag"},
 		},
-		OnUsageError: func(_ *Context, err error, isSubcommand bool) error {
+		OnUsageError: func(_ context.Context, _ *Command, err error, isSubcommand bool) error {
 			if isSubcommand {
 				t.Errorf("Expect subcommand")
 			}
@@ -2396,7 +2388,7 @@ func (c *customBoolFlag) Apply(set *flag.FlagSet) error {
 	return nil
 }
 
-func (c *customBoolFlag) RunAction(*Context) error {
+func (c *customBoolFlag) RunAction(context.Context, *Command) error {
 	return nil
 }
 
@@ -2470,13 +2462,14 @@ func TestCustomHelpVersionFlags(t *testing.T) {
 
 func TestHandleExitCoder_Default(t *testing.T) {
 	app := buildMinimalTestCommand()
-	fs, err := flagSet(app.Name, app.Flags)
+	fs, err := newFlagSet(app.Name, app.Flags)
 	if err != nil {
 		t.Errorf("error creating FlagSet: %s", err)
 	}
 
-	cCtx := NewContext(app, fs, nil)
-	_ = app.handleExitCoder(cCtx, Exit("Default Behavior Error", 42))
+	app.flagSet = fs
+
+	_ = app.handleExitCoder(context.Background(), Exit("Default Behavior Error", 42))
 
 	output := fakeErrWriter.String()
 	if !strings.Contains(output, "Default") {
@@ -2486,17 +2479,12 @@ func TestHandleExitCoder_Default(t *testing.T) {
 
 func TestHandleExitCoder_Custom(t *testing.T) {
 	cmd := buildMinimalTestCommand()
-	fs, err := flagSet(cmd.Name, cmd.Flags)
-	if err != nil {
-		t.Errorf("error creating FlagSet: %s", err)
-	}
 
-	cmd.ExitErrHandler = func(_ *Context, _ error) {
+	cmd.ExitErrHandler = func(context.Context, *Command, error) {
 		_, _ = fmt.Fprintln(ErrWriter, "I'm a Custom error handler, I print what I want!")
 	}
 
-	ctx := NewContext(cmd, fs, nil)
-	_ = cmd.handleExitCoder(ctx, Exit("Default Behavior Error", 42))
+	_ = cmd.handleExitCoder(context.Background(), Exit("Default Behavior Error", 42))
 
 	output := fakeErrWriter.String()
 	if !strings.Contains(output, "Custom") {
@@ -2512,18 +2500,18 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) {
 			},
 		},
 		EnableShellCompletion: true,
-		ShellComplete: func(ctx *Context) {
-			for _, command := range ctx.Command.Commands {
+		ShellComplete: func(_ context.Context, cmd *Command) {
+			for _, command := range cmd.Commands {
 				if command.Hidden {
 					continue
 				}
 
 				for _, name := range command.Names() {
-					_, _ = fmt.Fprintln(ctx.Command.Writer, name)
+					_, _ = fmt.Fprintln(cmd.Writer, name)
 				}
 			}
 
-			for _, fl := range ctx.Command.Flags {
+			for _, fl := range cmd.Flags {
 				for _, name := range fl.Names() {
 					if name == GenerateShellCompletionFlag.Names()[0] {
 						continue
@@ -2532,14 +2520,14 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) {
 					switch name = strings.TrimSpace(name); len(name) {
 					case 0:
 					case 1:
-						_, _ = fmt.Fprintln(ctx.Command.Writer, "-"+name)
+						_, _ = fmt.Fprintln(cmd.Writer, "-"+name)
 					default:
-						_, _ = fmt.Fprintln(ctx.Command.Writer, "--"+name)
+						_, _ = fmt.Fprintln(cmd.Writer, "--"+name)
 					}
 				}
 			}
 		},
-		Action: func(ctx *Context) error {
+		Action: func(context.Context, *Command) error {
 			return fmt.Errorf("should not get here")
 		},
 		Writer: io.Discard,
@@ -2561,7 +2549,7 @@ func TestWhenExitSubCommandWithCodeThenCommandQuitUnexpectedly(t *testing.T) {
 			Commands: []*Command{
 				{
 					Name: "subcmd",
-					Action: func(c *Context) error {
+					Action: func(context.Context, *Command) error {
 						return Exit("exit error", testCode)
 					},
 				},
@@ -2571,7 +2559,7 @@ func TestWhenExitSubCommandWithCodeThenCommandQuitUnexpectedly(t *testing.T) {
 
 	// set user function as ExitErrHandler
 	exitCodeFromExitErrHandler := int(0)
-	cmd.ExitErrHandler = func(_ *Context, err error) {
+	cmd.ExitErrHandler = func(_ context.Context, _ *Command, err error) {
 		if exitErr, ok := err.(ExitCoder); ok {
 			exitCodeFromExitErrHandler = exitErr.ExitCode()
 		}
@@ -2779,11 +2767,11 @@ func TestFlagAction(t *testing.T) {
 
 			stringFlag := &StringFlag{
 				Name: "f_string",
-				Action: func(cCtx *Context, v string) error {
+				Action: func(_ context.Context, cmd *Command, v string) error {
 					if v == "" {
 						return fmt.Errorf("empty string")
 					}
-					_, err := cCtx.Command.Root().Writer.Write([]byte(v + " "))
+					_, err := cmd.Root().Writer.Write([]byte(v + " "))
 					return err
 				},
 			}
@@ -2795,11 +2783,11 @@ func TestFlagAction(t *testing.T) {
 					{
 						Name:   "c1",
 						Flags:  []Flag{stringFlag},
-						Action: func(cCtx *Context) error { return nil },
+						Action: func(_ context.Context, cmd *Command) error { return nil },
 						Commands: []*Command{
 							{
 								Name:   "sub1",
-								Action: func(cCtx *Context) error { return nil },
+								Action: func(context.Context, *Command) error { return nil },
 								Flags:  []Flag{stringFlag},
 							},
 						},
@@ -2812,71 +2800,71 @@ func TestFlagAction(t *testing.T) {
 					},
 					&StringSliceFlag{
 						Name: "f_string_slice",
-						Action: func(cCtx *Context, v []string) error {
+						Action: func(_ context.Context, cmd *Command, v []string) error {
 							if v[0] == "err" {
 								return fmt.Errorf("error string slice")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+							_, err := cmd.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
 							return err
 						},
 					},
 					&BoolFlag{
 						Name: "f_bool",
-						Action: func(cCtx *Context, v bool) error {
+						Action: func(_ context.Context, cmd *Command, v bool) error {
 							if !v {
 								return fmt.Errorf("value is false")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(fmt.Sprintf("%t ", v)))
+							_, err := cmd.Root().Writer.Write([]byte(fmt.Sprintf("%t ", v)))
 							return err
 						},
 					},
 					&DurationFlag{
 						Name: "f_duration",
-						Action: func(cCtx *Context, v time.Duration) error {
+						Action: func(_ context.Context, cmd *Command, v time.Duration) error {
 							if v == 0 {
 								return fmt.Errorf("empty duration")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(v.String() + " "))
+							_, err := cmd.Root().Writer.Write([]byte(v.String() + " "))
 							return err
 						},
 					},
 					&FloatFlag{
 						Name: "f_float64",
-						Action: func(cCtx *Context, v float64) error {
+						Action: func(_ context.Context, cmd *Command, v float64) error {
 							if v < 0 {
 								return fmt.Errorf("negative float64")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(strconv.FormatFloat(v, 'f', -1, 64) + " "))
+							_, err := cmd.Root().Writer.Write([]byte(strconv.FormatFloat(v, 'f', -1, 64) + " "))
 							return err
 						},
 					},
 					&FloatSliceFlag{
 						Name: "f_float64_slice",
-						Action: func(cCtx *Context, v []float64) error {
+						Action: func(_ context.Context, cmd *Command, v []float64) error {
 							if len(v) > 0 && v[0] < 0 {
 								return fmt.Errorf("invalid float64 slice")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+							_, err := cmd.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
 							return err
 						},
 					},
 					&IntFlag{
 						Name: "f_int",
-						Action: func(cCtx *Context, v int64) error {
+						Action: func(_ context.Context, cmd *Command, v int64) error {
 							if v < 0 {
 								return fmt.Errorf("negative int")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+							_, err := cmd.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
 							return err
 						},
 					},
 					&IntSliceFlag{
 						Name: "f_int_slice",
-						Action: func(cCtx *Context, v []int64) error {
+						Action: func(_ context.Context, cmd *Command, v []int64) error {
 							if len(v) > 0 && v[0] < 0 {
 								return fmt.Errorf("invalid int slice")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+							_, err := cmd.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
 							return err
 						},
 					},
@@ -2885,36 +2873,36 @@ func TestFlagAction(t *testing.T) {
 						Config: TimestampConfig{
 							Layout: "2006-01-02 15:04:05",
 						},
-						Action: func(cCtx *Context, v time.Time) error {
+						Action: func(_ context.Context, cmd *Command, v time.Time) error {
 							if v.IsZero() {
 								return fmt.Errorf("zero timestamp")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(v.Format(time.RFC3339) + " "))
+							_, err := cmd.Root().Writer.Write([]byte(v.Format(time.RFC3339) + " "))
 							return err
 						},
 					},
 					&UintFlag{
 						Name: "f_uint",
-						Action: func(cCtx *Context, v uint64) error {
+						Action: func(_ context.Context, cmd *Command, v uint64) error {
 							if v == 0 {
 								return fmt.Errorf("zero uint64")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
+							_, err := cmd.Root().Writer.Write([]byte(fmt.Sprintf("%v ", v)))
 							return err
 						},
 					},
 					&StringMapFlag{
 						Name: "f_string_map",
-						Action: func(cCtx *Context, v map[string]string) error {
+						Action: func(_ context.Context, cmd *Command, v map[string]string) error {
 							if _, ok := v["err"]; ok {
 								return fmt.Errorf("error string map")
 							}
-							_, err := cCtx.Command.Root().Writer.Write([]byte(fmt.Sprintf("%v", v)))
+							_, err := cmd.Root().Writer.Write([]byte(fmt.Sprintf("%v", v)))
 							return err
 						},
 					},
 				},
-				Action: func(cCtx *Context) error { return nil },
+				Action: func(context.Context, *Command) error { return nil },
 			}
 
 			err := cmd.Run(buildTestContext(t), test.args)
@@ -2946,7 +2934,7 @@ func TestPersistentFlag(t *testing.T) {
 				Name:        "persistentCommandFlag",
 				Persistent:  true,
 				Destination: &appFlag,
-				Action: func(ctx *Context, s string) error {
+				Action: func(context.Context, *Command, string) error {
 					persistentFlagActionCount++
 					return nil
 				},
@@ -2995,8 +2983,8 @@ func TestPersistentFlag(t *testing.T) {
 								Destination: &subCommandInt,
 							},
 						},
-						Action: func(ctx *Context) error {
-							appSliceFloat64 = ctx.FloatSlice("persistentCommandFloatSliceFlag")
+						Action: func(_ context.Context, cmd *Command) error {
+							appSliceFloat64 = cmd.FloatSlice("persistentCommandFloatSliceFlag")
 							return nil
 						},
 					},
@@ -3068,7 +3056,6 @@ func TestPersistentFlag(t *testing.T) {
 }
 
 func TestFlagDuplicates(t *testing.T) {
-
 	tests := []struct {
 		name        string
 		args        []string
@@ -3113,7 +3100,7 @@ func TestFlagDuplicates(t *testing.T) {
 						Name: "iflag",
 					},
 				},
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			}
@@ -3130,7 +3117,7 @@ func TestFlagDuplicates(t *testing.T) {
 
 func TestShorthandCommand(t *testing.T) {
 	af := func(p *int) ActionFunc {
-		return func(ctx *Context) error {
+		return func(context.Context, *Command) error {
 			*p = *p + 1
 			return nil
 		}
@@ -3211,3 +3198,588 @@ func TestShorthandCommand(t *testing.T) {
 		t.Errorf("Expected command1 to be trigerred once but didnt %d %d", cmd1, cmd2)
 	}
 }
+
+func TestCommand_Int(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Int64("myflag", 12, "doc")
+
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Int64("top-flag", 13, "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+
+	r := require.New(t)
+	r.Equal(int64(12), cmd.Int("myflag"))
+	r.Equal(int64(13), cmd.Int("top-flag"))
+}
+
+func TestCommand_Uint(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Uint64("myflagUint", uint64(13), "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Uint64("top-flag", uint64(14), "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+
+	r := require.New(t)
+	r.Equal(uint64(13), cmd.Uint("myflagUint"))
+	r.Equal(uint64(14), cmd.Uint("top-flag"))
+}
+
+func TestCommand_Float64(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Float64("myflag", float64(17), "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Float64("top-flag", float64(18), "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+
+	r := require.New(t)
+	r.Equal(float64(17), cmd.Float("myflag"))
+	r.Equal(float64(18), cmd.Float("top-flag"))
+}
+
+func TestCommand_Duration(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Duration("myflag", 12*time.Second, "doc")
+
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Duration("top-flag", 13*time.Second, "doc")
+	pCmd := &Command{flagSet: parentSet}
+
+	cmd := &Command{flagSet: set, parent: pCmd}
+
+	r := require.New(t)
+	r.Equal(12*time.Second, cmd.Duration("myflag"))
+	r.Equal(13*time.Second, cmd.Duration("top-flag"))
+}
+
+func TestCommand_String(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.String("myflag", "hello world", "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.String("top-flag", "hai veld", "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+
+	r := require.New(t)
+	r.Equal("hello world", cmd.String("myflag"))
+	r.Equal("hai veld", cmd.String("top-flag"))
+
+	cmd = &Command{parent: pCmd}
+	r.Equal("hai veld", cmd.String("top-flag"))
+}
+
+func TestCommand_Bool(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("myflag", false, "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Bool("top-flag", true, "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+
+	r := require.New(t)
+	r.False(cmd.Bool("myflag"))
+	r.True(cmd.Bool("top-flag"))
+}
+
+func TestCommand_Value(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Int("myflag", 12, "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Int("top-flag", 13, "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+
+	r := require.New(t)
+	r.Equal(12, cmd.Value("myflag"))
+	r.Equal(13, cmd.Value("top-flag"))
+	r.Equal(nil, cmd.Value("unknown-flag"))
+}
+
+func TestCommand_Value_InvalidFlagAccessHandler(t *testing.T) {
+	var flagName string
+	cmd := &Command{
+		InvalidFlagAccessHandler: func(_ context.Context, _ *Command, name string) {
+			flagName = name
+		},
+		Commands: []*Command{
+			{
+				Name: "command",
+				Commands: []*Command{
+					{
+						Name: "subcommand",
+						Action: func(_ context.Context, cmd *Command) error {
+							cmd.Value("missing")
+							return nil
+						},
+					},
+				},
+			},
+		},
+	}
+
+	r := require.New(t)
+
+	r.NoError(cmd.Run(buildTestContext(t), []string{"run", "command", "subcommand"}))
+	r.Equal("missing", flagName)
+}
+
+func TestCommand_Args(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("myflag", false, "doc")
+	cmd := &Command{flagSet: set}
+	_ = set.Parse([]string{"--myflag", "bat", "baz"})
+
+	r := require.New(t)
+	r.Equal(2, cmd.Args().Len())
+	r.True(cmd.Bool("myflag"))
+}
+
+func TestCommand_NArg(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("myflag", false, "doc")
+	cmd := &Command{flagSet: set}
+	_ = set.Parse([]string{"--myflag", "bat", "baz"})
+
+	require.Equal(t, 2, cmd.NArg())
+}
+
+func TestCommand_IsSet(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("one-flag", false, "doc")
+	set.Bool("two-flag", false, "doc")
+	set.String("three-flag", "hello world", "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Bool("top-flag", true, "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+
+	_ = set.Parse([]string{"--one-flag", "--two-flag", "--three-flag", "frob"})
+	_ = parentSet.Parse([]string{"--top-flag"})
+
+	r := require.New(t)
+
+	r.True(cmd.IsSet("one-flag"))
+	r.True(cmd.IsSet("two-flag"))
+	r.True(cmd.IsSet("three-flag"))
+	r.True(cmd.IsSet("top-flag"))
+	r.False(cmd.IsSet("bogus"))
+}
+
+// XXX Corresponds to hack in context.IsSet for flags with EnvVar field
+// Should be moved to `flag_test` in v2
+func TestCommand_IsSet_fromEnv(t *testing.T) {
+	var (
+		timeoutIsSet, tIsSet    bool
+		noEnvVarIsSet, nIsSet   bool
+		passwordIsSet, pIsSet   bool
+		unparsableIsSet, uIsSet bool
+	)
+
+	t.Setenv("APP_TIMEOUT_SECONDS", "15.5")
+	t.Setenv("APP_PASSWORD", "")
+
+	cmd := &Command{
+		Flags: []Flag{
+			&FloatFlag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("APP_TIMEOUT_SECONDS")},
+			&StringFlag{Name: "password", Aliases: []string{"p"}, Sources: EnvVars("APP_PASSWORD")},
+			&FloatFlag{Name: "unparsable", Aliases: []string{"u"}, Sources: EnvVars("APP_UNPARSABLE")},
+			&FloatFlag{Name: "no-env-var", Aliases: []string{"n"}},
+		},
+		Action: func(_ context.Context, cmd *Command) error {
+			timeoutIsSet = cmd.IsSet("timeout")
+			tIsSet = cmd.IsSet("t")
+			passwordIsSet = cmd.IsSet("password")
+			pIsSet = cmd.IsSet("p")
+			unparsableIsSet = cmd.IsSet("unparsable")
+			uIsSet = cmd.IsSet("u")
+			noEnvVarIsSet = cmd.IsSet("no-env-var")
+			nIsSet = cmd.IsSet("n")
+			return nil
+		},
+	}
+
+	r := require.New(t)
+
+	r.NoError(cmd.Run(buildTestContext(t), []string{"run"}))
+	r.True(timeoutIsSet)
+	r.True(tIsSet)
+	r.True(passwordIsSet)
+	r.True(pIsSet)
+	r.False(noEnvVarIsSet)
+	r.False(nIsSet)
+
+	t.Setenv("APP_UNPARSABLE", "foobar")
+
+	r.Error(cmd.Run(buildTestContext(t), []string{"run"}))
+	r.False(unparsableIsSet)
+	r.False(uIsSet)
+}
+
+func TestCommand_NumFlags(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("myflag", false, "doc")
+	set.String("otherflag", "hello world", "doc")
+	globalSet := flag.NewFlagSet("test", 0)
+	globalSet.Bool("myflagGlobal", true, "doc")
+	globalCmd := &Command{flagSet: globalSet}
+	cmd := &Command{flagSet: set, parent: globalCmd}
+	_ = set.Parse([]string{"--myflag", "--otherflag=foo"})
+	_ = globalSet.Parse([]string{"--myflagGlobal"})
+	require.Equal(t, 2, cmd.NumFlags())
+}
+
+func TestCommand_Set(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Int64("int", int64(5), "an int")
+	cmd := &Command{flagSet: set}
+
+	r := require.New(t)
+
+	r.False(cmd.IsSet("int"))
+	r.NoError(cmd.Set("int", "1"))
+	r.Equal(int64(1), cmd.Int("int"))
+	r.True(cmd.IsSet("int"))
+}
+
+func TestCommand_Set_InvalidFlagAccessHandler(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	var flagName string
+	cmd := &Command{
+		InvalidFlagAccessHandler: func(_ context.Context, _ *Command, name string) {
+			flagName = name
+		},
+		flagSet: set,
+	}
+
+	r := require.New(t)
+
+	r.True(cmd.Set("missing", "") != nil)
+	r.Equal("missing", flagName)
+}
+
+func TestCommand_LocalFlagNames(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("one-flag", false, "doc")
+	set.String("two-flag", "hello world", "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Bool("top-flag", true, "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+	_ = set.Parse([]string{"--one-flag", "--two-flag=foo"})
+	_ = parentSet.Parse([]string{"--top-flag"})
+
+	actualFlags := cmd.LocalFlagNames()
+	sort.Strings(actualFlags)
+
+	require.Equal(t, []string{"one-flag", "two-flag"}, actualFlags)
+}
+
+func TestCommand_FlagNames(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("one-flag", false, "doc")
+	set.String("two-flag", "hello world", "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Bool("top-flag", true, "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+	_ = set.Parse([]string{"--one-flag", "--two-flag=foo"})
+	_ = parentSet.Parse([]string{"--top-flag"})
+
+	actualFlags := cmd.FlagNames()
+	sort.Strings(actualFlags)
+
+	require.Equal(t, []string{"one-flag", "top-flag", "two-flag"}, actualFlags)
+}
+
+func TestCommand_Lineage(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("local-flag", false, "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Bool("top-flag", true, "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+	_ = set.Parse([]string{"--local-flag"})
+	_ = parentSet.Parse([]string{"--top-flag"})
+
+	lineage := cmd.Lineage()
+
+	r := require.New(t)
+	r.Equal(2, len(lineage))
+	r.Equal(cmd, lineage[0])
+	r.Equal(pCmd, lineage[1])
+}
+
+func TestCommand_lookupFlagSet(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Bool("local-flag", false, "doc")
+	parentSet := flag.NewFlagSet("test", 0)
+	parentSet.Bool("top-flag", true, "doc")
+	pCmd := &Command{flagSet: parentSet}
+	cmd := &Command{flagSet: set, parent: pCmd}
+	_ = set.Parse([]string{"--local-flag"})
+	_ = parentSet.Parse([]string{"--top-flag"})
+
+	r := require.New(t)
+
+	fs := cmd.lookupFlagSet("top-flag")
+	r.Equal(pCmd.flagSet, fs)
+
+	fs = cmd.lookupFlagSet("local-flag")
+	r.Equal(cmd.flagSet, fs)
+	r.Nil(cmd.lookupFlagSet("frob"))
+}
+
+func TestCommandAttributeAccessing(t *testing.T) {
+	tdata := []struct {
+		testCase     string
+		setBoolInput string
+		ctxBoolInput string
+		parent       *Command
+	}{
+		{
+			testCase:     "empty",
+			setBoolInput: "",
+			ctxBoolInput: "",
+		},
+		{
+			testCase:     "empty_with_background_context",
+			setBoolInput: "",
+			ctxBoolInput: "",
+			parent:       &Command{},
+		},
+		{
+			testCase:     "empty_set_bool_and_present_ctx_bool",
+			setBoolInput: "",
+			ctxBoolInput: "ctx-bool",
+		},
+		{
+			testCase:     "present_set_bool_and_present_ctx_bool_with_background_context",
+			setBoolInput: "",
+			ctxBoolInput: "ctx-bool",
+			parent:       &Command{},
+		},
+		{
+			testCase:     "present_set_bool_and_present_ctx_bool",
+			setBoolInput: "ctx-bool",
+			ctxBoolInput: "ctx-bool",
+		},
+		{
+			testCase:     "present_set_bool_and_present_ctx_bool_with_background_context",
+			setBoolInput: "ctx-bool",
+			ctxBoolInput: "ctx-bool",
+			parent:       &Command{},
+		},
+		{
+			testCase:     "present_set_bool_and_different_ctx_bool",
+			setBoolInput: "ctx-bool",
+			ctxBoolInput: "not-ctx-bool",
+		},
+		{
+			testCase:     "present_set_bool_and_different_ctx_bool_with_background_context",
+			setBoolInput: "ctx-bool",
+			ctxBoolInput: "not-ctx-bool",
+			parent:       &Command{},
+		},
+	}
+
+	for _, test := range tdata {
+		t.Run(test.testCase, func(t *testing.T) {
+			set := flag.NewFlagSet("some-flag-set-name", 0)
+			set.Bool(test.setBoolInput, false, "usage documentation")
+			cmd := &Command{flagSet: set, parent: test.parent}
+
+			require.False(t, cmd.Bool(test.ctxBoolInput))
+		})
+	}
+}
+
+func TestCheckRequiredFlags(t *testing.T) {
+	tdata := []struct {
+		testCase              string
+		parseInput            []string
+		envVarInput           [2]string
+		flags                 []Flag
+		expectedAnError       bool
+		expectedErrorContents []string
+	}{
+		{
+			testCase: "empty",
+		},
+		{
+			testCase: "optional",
+			flags: []Flag{
+				&StringFlag{Name: "optionalFlag"},
+			},
+		},
+		{
+			testCase: "required",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true},
+			},
+			expectedAnError:       true,
+			expectedErrorContents: []string{"requiredFlag"},
+		},
+		{
+			testCase: "required_and_present",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true},
+			},
+			parseInput: []string{"--requiredFlag", "myinput"},
+		},
+		{
+			testCase: "required_and_present_via_env_var",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true, Sources: EnvVars("REQUIRED_FLAG")},
+			},
+			envVarInput: [2]string{"REQUIRED_FLAG", "true"},
+		},
+		{
+			testCase: "required_and_optional",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true},
+				&StringFlag{Name: "optionalFlag"},
+			},
+			expectedAnError: true,
+		},
+		{
+			testCase: "required_and_optional_and_optional_present",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true},
+				&StringFlag{Name: "optionalFlag"},
+			},
+			parseInput:      []string{"--optionalFlag", "myinput"},
+			expectedAnError: true,
+		},
+		{
+			testCase: "required_and_optional_and_optional_present_via_env_var",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true},
+				&StringFlag{Name: "optionalFlag", Sources: EnvVars("OPTIONAL_FLAG")},
+			},
+			envVarInput:     [2]string{"OPTIONAL_FLAG", "true"},
+			expectedAnError: true,
+		},
+		{
+			testCase: "required_and_optional_and_required_present",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true},
+				&StringFlag{Name: "optionalFlag"},
+			},
+			parseInput: []string{"--requiredFlag", "myinput"},
+		},
+		{
+			testCase: "two_required",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlagOne", Required: true},
+				&StringFlag{Name: "requiredFlagTwo", Required: true},
+			},
+			expectedAnError:       true,
+			expectedErrorContents: []string{"requiredFlagOne", "requiredFlagTwo"},
+		},
+		{
+			testCase: "two_required_and_one_present",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true},
+				&StringFlag{Name: "requiredFlagTwo", Required: true},
+			},
+			parseInput:      []string{"--requiredFlag", "myinput"},
+			expectedAnError: true,
+		},
+		{
+			testCase: "two_required_and_both_present",
+			flags: []Flag{
+				&StringFlag{Name: "requiredFlag", Required: true},
+				&StringFlag{Name: "requiredFlagTwo", Required: true},
+			},
+			parseInput: []string{"--requiredFlag", "myinput", "--requiredFlagTwo", "myinput"},
+		},
+		{
+			testCase: "required_flag_with_short_name",
+			flags: []Flag{
+				&StringSliceFlag{Name: "names", Aliases: []string{"N"}, Required: true},
+			},
+			parseInput: []string{"-N", "asd", "-N", "qwe"},
+		},
+		{
+			testCase: "required_flag_with_multiple_short_names",
+			flags: []Flag{
+				&StringSliceFlag{Name: "names", Aliases: []string{"N", "n"}, Required: true},
+			},
+			parseInput: []string{"-n", "asd", "-n", "qwe"},
+		},
+		{
+			testCase:              "required_flag_with_short_alias_not_printed_on_error",
+			expectedAnError:       true,
+			expectedErrorContents: []string{"Required flag \"names\" not set"},
+			flags: []Flag{
+				&StringSliceFlag{Name: "names, n", Required: true},
+			},
+		},
+		{
+			testCase:              "required_flag_with_one_character",
+			expectedAnError:       true,
+			expectedErrorContents: []string{"Required flag \"n\" not set"},
+			flags: []Flag{
+				&StringFlag{Name: "n", Required: true},
+			},
+		},
+	}
+
+	for _, test := range tdata {
+		t.Run(test.testCase, func(t *testing.T) {
+			// setup
+			if test.envVarInput[0] != "" {
+				defer resetEnv(os.Environ())
+				os.Clearenv()
+				_ = os.Setenv(test.envVarInput[0], test.envVarInput[1])
+			}
+
+			set := flag.NewFlagSet("test", 0)
+			for _, flags := range test.flags {
+				_ = flags.Apply(set)
+			}
+			_ = set.Parse(test.parseInput)
+
+			cmd := &Command{
+				Flags:   test.flags,
+				flagSet: set,
+			}
+
+			err := cmd.checkRequiredFlags()
+
+			// assertions
+			if test.expectedAnError && err == nil {
+				t.Errorf("expected an error, but there was none")
+			}
+			if !test.expectedAnError && err != nil {
+				t.Errorf("did not expected an error, but there was one: %s", err)
+			}
+			for _, errString := range test.expectedErrorContents {
+				if err != nil {
+					if !strings.Contains(err.Error(), errString) {
+						t.Errorf("expected error %q to contain %q, but it didn't!", err.Error(), errString)
+					}
+				}
+			}
+		})
+	}
+}
+
+func TestCommand_ParentCommand_Set(t *testing.T) {
+	parentSet := flag.NewFlagSet("parent", flag.ContinueOnError)
+	parentSet.String("Name", "", "")
+
+	cmd := &Command{
+		flagSet: flag.NewFlagSet("child", flag.ContinueOnError),
+		parent: &Command{
+			flagSet: parentSet,
+		},
+	}
+
+	err := cmd.Set("Name", "aaa")
+	if err != nil {
+		t.Errorf("expect nil. set parent context flag return err: %s", err)
+	}
+}
diff --git a/completion.go b/completion.go
index 06998b9b46..2319a815c9 100644
--- a/completion.go
+++ b/completion.go
@@ -1,6 +1,7 @@
 package cli
 
 import (
+	"context"
 	"embed"
 	"fmt"
 	"sort"
@@ -41,7 +42,7 @@ func buildCompletionCommand() *Command {
 	}
 }
 
-func completionCommandAction(cCtx *Context) error {
+func completionCommandAction(ctx context.Context, cmd *Command) error {
 	var shells []string
 	for k := range shellCompletions {
 		shells = append(shells, k)
@@ -49,17 +50,17 @@ func completionCommandAction(cCtx *Context) error {
 
 	sort.Strings(shells)
 
-	if cCtx.Args().Len() == 0 {
+	if cmd.Args().Len() == 0 {
 		return Exit(fmt.Sprintf("no shell provided for completion command. available shells are %+v", shells), 1)
 	}
-	s := cCtx.Args().First()
+	s := cmd.Args().First()
 
 	if rc, ok := shellCompletions[s]; !ok {
 		return Exit(fmt.Sprintf("unknown shell %s, available shells are %+v", s, shells), 1)
-	} else if c, err := rc(cCtx.Command); err != nil {
+	} else if c, err := rc(cmd); err != nil {
 		return Exit(err, 1)
 	} else {
-		if _, err = cCtx.Command.Writer.Write([]byte(c)); err != nil {
+		if _, err = cmd.Writer.Write([]byte(c)); err != nil {
 			return Exit(err, 1)
 		}
 	}
diff --git a/context.go b/context.go
deleted file mode 100644
index da1965d8a8..0000000000
--- a/context.go
+++ /dev/null
@@ -1,257 +0,0 @@
-package cli
-
-import (
-	"context"
-	"flag"
-	"fmt"
-	"strings"
-)
-
-const (
-	contextContextKey = contextKey("cli.context")
-)
-
-type contextKey string
-
-// Context is a type that is passed through to
-// each Handler action in a cli application. Context
-// can be used to retrieve context-specific args and
-// parsed command-line options.
-type Context struct {
-	context.Context
-	Command       *Command
-	shellComplete bool
-	flagSet       *flag.FlagSet
-	parent        *Context
-}
-
-// NewContext creates a new context. For use in when invoking a Command action.
-func NewContext(cmd *Command, set *flag.FlagSet, parent *Context) *Context {
-	cCtx := &Context{
-		Command: cmd,
-		flagSet: set,
-		parent:  parent,
-	}
-
-	if parent != nil {
-		cCtx.Context = parent.Context
-		cCtx.shellComplete = parent.shellComplete
-
-		if parent.flagSet == nil {
-			parent.flagSet = &flag.FlagSet{}
-		}
-	}
-
-	if cCtx.Command == nil {
-		cCtx.Command = &Command{}
-	}
-
-	if cCtx.Context == nil {
-		cCtx.Context = context.Background()
-	}
-
-	return cCtx
-}
-
-// NumFlags returns the number of flags set
-func (cCtx *Context) NumFlags() int {
-	return cCtx.flagSet.NFlag()
-}
-
-// Set sets a context flag to a value.
-func (cCtx *Context) Set(name, value string) error {
-	if fs := cCtx.lookupFlagSet(name); fs != nil {
-		return fs.Set(name, value)
-	}
-
-	return fmt.Errorf("no such flag -%s", name)
-}
-
-// IsSet determines if the flag was actually set
-func (cCtx *Context) IsSet(name string) bool {
-	if fs := cCtx.lookupFlagSet(name); fs != nil {
-		isSet := false
-		fs.Visit(func(f *flag.Flag) {
-			if f.Name == name {
-				isSet = true
-			}
-		})
-		if isSet {
-			return true
-		}
-
-		f := cCtx.lookupFlag(name)
-		if f == nil {
-			return false
-		}
-
-		return f.IsSet()
-	}
-
-	return false
-}
-
-// LocalFlagNames returns a slice of flag names used in this context.
-func (cCtx *Context) LocalFlagNames() []string {
-	var names []string
-	cCtx.flagSet.Visit(makeFlagNameVisitor(&names))
-	// Check the flags which have been set via env or file
-	if cCtx.Command != nil && cCtx.Command.Flags != nil {
-		for _, f := range cCtx.Command.Flags {
-			if f.IsSet() {
-				names = append(names, f.Names()...)
-			}
-		}
-	}
-
-	// Sort out the duplicates since flag could be set via multiple
-	// paths
-	m := map[string]struct{}{}
-	var unames []string
-	for _, name := range names {
-		if _, ok := m[name]; !ok {
-			m[name] = struct{}{}
-			unames = append(unames, name)
-		}
-	}
-
-	return unames
-}
-
-// FlagNames returns a slice of flag names used by the this context and all of
-// its parent contexts.
-func (cCtx *Context) FlagNames() []string {
-	var names []string
-	for _, pCtx := range cCtx.Lineage() {
-		names = append(names, pCtx.LocalFlagNames()...)
-	}
-	return names
-}
-
-// Lineage returns *this* context and all of its ancestor contexts in order from
-// child to parent
-func (cCtx *Context) Lineage() []*Context {
-	var lineage []*Context
-
-	for cur := cCtx; cur != nil; cur = cur.parent {
-		lineage = append(lineage, cur)
-	}
-
-	return lineage
-}
-
-// Count returns the num of occurrences of this flag
-func (cCtx *Context) Count(name string) int {
-	if fs := cCtx.lookupFlagSet(name); fs != nil {
-		if cf, ok := fs.Lookup(name).Value.(Countable); ok {
-			return cf.Count()
-		}
-	}
-	return 0
-}
-
-// Value returns the value of the flag corresponding to `name`
-func (cCtx *Context) Value(name string) interface{} {
-	if fs := cCtx.lookupFlagSet(name); fs != nil {
-		return fs.Lookup(name).Value.(flag.Getter).Get()
-	}
-	return nil
-}
-
-// Args returns the command line arguments associated with the context.
-func (cCtx *Context) Args() Args {
-	ret := args(cCtx.flagSet.Args())
-	return &ret
-}
-
-// NArg returns the number of the command line arguments.
-func (cCtx *Context) NArg() int {
-	return cCtx.Args().Len()
-}
-
-func (cCtx *Context) lookupFlag(name string) Flag {
-	for _, c := range cCtx.Lineage() {
-		if c.Command == nil {
-			continue
-		}
-
-		for _, f := range c.Command.Flags {
-			for _, n := range f.Names() {
-				if n == name {
-					return f
-				}
-			}
-		}
-	}
-
-	return nil
-}
-
-func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet {
-	for _, c := range cCtx.Lineage() {
-		if c.flagSet == nil {
-			continue
-		}
-		if f := c.flagSet.Lookup(name); f != nil {
-			return c.flagSet
-		}
-	}
-	cCtx.onInvalidFlag(name)
-	return nil
-}
-
-func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
-	var missingFlags []string
-	for _, f := range flags {
-		if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
-			var flagPresent bool
-			var flagName string
-
-			for _, key := range f.Names() {
-				flagName = key
-
-				if cCtx.IsSet(strings.TrimSpace(key)) {
-					flagPresent = true
-				}
-			}
-
-			if !flagPresent && flagName != "" {
-				missingFlags = append(missingFlags, flagName)
-			}
-		}
-	}
-
-	if len(missingFlags) != 0 {
-		return &errRequiredFlags{missingFlags: missingFlags}
-	}
-
-	return nil
-}
-
-func (cCtx *Context) onInvalidFlag(name string) {
-	for cCtx != nil {
-		if cCtx.Command != nil && cCtx.Command.InvalidFlagAccessHandler != nil {
-			cCtx.Command.InvalidFlagAccessHandler(cCtx, name)
-			break
-		}
-		cCtx = cCtx.parent
-	}
-}
-
-func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
-	return func(f *flag.Flag) {
-		nameParts := strings.Split(f.Name, ",")
-		name := strings.TrimSpace(nameParts[0])
-
-		for _, part := range nameParts {
-			part = strings.TrimSpace(part)
-			if len(part) > len(name) {
-				name = part
-			}
-		}
-
-		if name != "" {
-			*names = append(*names, name)
-		}
-	}
-}
diff --git a/context_test.go b/context_test.go
deleted file mode 100644
index 315b91b46c..0000000000
--- a/context_test.go
+++ /dev/null
@@ -1,642 +0,0 @@
-package cli
-
-import (
-	"context"
-	"flag"
-	"os"
-	"sort"
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestNewContext(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Int64("myflag", 12, "doc")
-	set.Uint64("myflagUint", uint64(93), "doc")
-	set.Float64("myflag64", float64(17), "doc")
-
-	globalSet := flag.NewFlagSet("test", 0)
-	globalSet.Int64("myflag", 42, "doc")
-	globalSet.Uint64("myflagUint", uint64(33), "doc")
-	globalSet.Float64("myflag64", float64(47), "doc")
-
-	globalCtx := NewContext(nil, globalSet, nil)
-
-	command := &Command{Name: "mycommand"}
-	cCtx := NewContext(nil, set, globalCtx)
-	cCtx.Command = command
-
-	r := require.New(t)
-	r.Equal(int64(12), cCtx.Int("myflag"))
-	r.Equal(uint64(93), cCtx.Uint("myflagUint"))
-	r.Equal(float64(17), cCtx.Float("myflag64"))
-	r.Equal("mycommand", cCtx.Command.Name)
-}
-
-func TestContext_Int(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Int64("myflag", 12, "doc")
-
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Int64("top-flag", 13, "doc")
-	parentCctx := NewContext(nil, parentSet, nil)
-
-	cCtx := NewContext(nil, set, parentCctx)
-
-	r := require.New(t)
-	r.Equal(int64(12), cCtx.Int("myflag"))
-	r.Equal(int64(13), cCtx.Int("top-flag"))
-}
-
-func TestContext_Uint(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Uint64("myflagUint", uint64(13), "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Uint64("top-flag", uint64(14), "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	cCtx := NewContext(nil, set, parentCtx)
-
-	r := require.New(t)
-	r.Equal(uint64(13), cCtx.Uint("myflagUint"))
-	r.Equal(uint64(14), cCtx.Uint("top-flag"))
-}
-
-func TestContext_Float64(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Float64("myflag", float64(17), "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Float64("top-flag", float64(18), "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	c := NewContext(nil, set, parentCtx)
-	expect(t, c.Float("myflag"), float64(17))
-	expect(t, c.Float("top-flag"), float64(18))
-}
-
-func TestContext_Duration(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Duration("myflag", 12*time.Second, "doc")
-
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Duration("top-flag", 13*time.Second, "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-
-	c := NewContext(nil, set, parentCtx)
-	expect(t, c.Duration("myflag"), 12*time.Second)
-	expect(t, c.Duration("top-flag"), 13*time.Second)
-}
-
-func TestContext_String(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.String("myflag", "hello world", "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.String("top-flag", "hai veld", "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	c := NewContext(nil, set, parentCtx)
-	expect(t, c.String("myflag"), "hello world")
-	expect(t, c.String("top-flag"), "hai veld")
-	c = NewContext(nil, nil, parentCtx)
-	expect(t, c.String("top-flag"), "hai veld")
-}
-
-func TestContext_Bool(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("myflag", false, "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Bool("top-flag", true, "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	c := NewContext(nil, set, parentCtx)
-	expect(t, c.Bool("myflag"), false)
-	expect(t, c.Bool("top-flag"), true)
-}
-
-func TestContext_Value(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Int("myflag", 12, "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Int("top-flag", 13, "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	c := NewContext(nil, set, parentCtx)
-	expect(t, c.Value("myflag"), 12)
-	expect(t, c.Value("top-flag"), 13)
-	expect(t, c.Value("unknown-flag"), nil)
-}
-
-func TestContext_Value_InvalidFlagAccessHandler(t *testing.T) {
-	var flagName string
-	cmd := &Command{
-		InvalidFlagAccessHandler: func(_ *Context, name string) {
-			flagName = name
-		},
-		Commands: []*Command{
-			{
-				Name: "command",
-				Commands: []*Command{
-					{
-						Name: "subcommand",
-						Action: func(ctx *Context) error {
-							ctx.Value("missing")
-							return nil
-						},
-					},
-				},
-			},
-		},
-	}
-
-	expect(t, cmd.Run(buildTestContext(t), []string{"run", "command", "subcommand"}), nil)
-	expect(t, flagName, "missing")
-}
-
-func TestContext_Args(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("myflag", false, "doc")
-	c := NewContext(nil, set, nil)
-	_ = set.Parse([]string{"--myflag", "bat", "baz"})
-	expect(t, c.Args().Len(), 2)
-	expect(t, c.Bool("myflag"), true)
-}
-
-func TestContext_NArg(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("myflag", false, "doc")
-	c := NewContext(nil, set, nil)
-	_ = set.Parse([]string{"--myflag", "bat", "baz"})
-	expect(t, c.NArg(), 2)
-}
-
-func TestContext_IsSet(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("one-flag", false, "doc")
-	set.Bool("two-flag", false, "doc")
-	set.String("three-flag", "hello world", "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Bool("top-flag", true, "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	ctx := NewContext(nil, set, parentCtx)
-
-	_ = set.Parse([]string{"--one-flag", "--two-flag", "--three-flag", "frob"})
-	_ = parentSet.Parse([]string{"--top-flag"})
-
-	expect(t, ctx.IsSet("one-flag"), true)
-	expect(t, ctx.IsSet("two-flag"), true)
-	expect(t, ctx.IsSet("three-flag"), true)
-	expect(t, ctx.IsSet("top-flag"), true)
-	expect(t, ctx.IsSet("bogus"), false)
-}
-
-// XXX Corresponds to hack in context.IsSet for flags with EnvVar field
-// Should be moved to `flag_test` in v2
-func TestContext_IsSet_fromEnv(t *testing.T) {
-	var (
-		timeoutIsSet, tIsSet    bool
-		noEnvVarIsSet, nIsSet   bool
-		passwordIsSet, pIsSet   bool
-		unparsableIsSet, uIsSet bool
-	)
-
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
-	_ = os.Setenv("APP_PASSWORD", "")
-	cmd := &Command{
-		Flags: []Flag{
-			&FloatFlag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("APP_TIMEOUT_SECONDS")},
-			&StringFlag{Name: "password", Aliases: []string{"p"}, Sources: EnvVars("APP_PASSWORD")},
-			&FloatFlag{Name: "unparsable", Aliases: []string{"u"}, Sources: EnvVars("APP_UNPARSABLE")},
-			&FloatFlag{Name: "no-env-var", Aliases: []string{"n"}},
-		},
-		Action: func(ctx *Context) error {
-			timeoutIsSet = ctx.IsSet("timeout")
-			tIsSet = ctx.IsSet("t")
-			passwordIsSet = ctx.IsSet("password")
-			pIsSet = ctx.IsSet("p")
-			unparsableIsSet = ctx.IsSet("unparsable")
-			uIsSet = ctx.IsSet("u")
-			noEnvVarIsSet = ctx.IsSet("no-env-var")
-			nIsSet = ctx.IsSet("n")
-			return nil
-		},
-	}
-
-	_ = cmd.Run(buildTestContext(t), []string{"run"})
-	expect(t, timeoutIsSet, true)
-	expect(t, tIsSet, true)
-	expect(t, passwordIsSet, true)
-	expect(t, pIsSet, true)
-	expect(t, noEnvVarIsSet, false)
-	expect(t, nIsSet, false)
-
-	t.Setenv("APP_UNPARSABLE", "foobar")
-	_ = cmd.Run(buildTestContext(t), []string{"run"})
-	expect(t, unparsableIsSet, false)
-	expect(t, uIsSet, false)
-}
-
-func TestContext_NumFlags(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("myflag", false, "doc")
-	set.String("otherflag", "hello world", "doc")
-	globalSet := flag.NewFlagSet("test", 0)
-	globalSet.Bool("myflagGlobal", true, "doc")
-	globalCtx := NewContext(nil, globalSet, nil)
-	c := NewContext(nil, set, globalCtx)
-	_ = set.Parse([]string{"--myflag", "--otherflag=foo"})
-	_ = globalSet.Parse([]string{"--myflagGlobal"})
-	expect(t, c.NumFlags(), 2)
-}
-
-func TestContext_Set(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Int64("int", int64(5), "an int")
-	cCtx := NewContext(nil, set, nil)
-
-	r := require.New(t)
-
-	r.False(cCtx.IsSet("int"))
-	r.NoError(cCtx.Set("int", "1"))
-	r.Equal(int64(1), cCtx.Int("int"))
-	r.True(cCtx.IsSet("int"))
-}
-
-func TestContext_Set_InvalidFlagAccessHandler(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	var flagName string
-	cmd := &Command{
-		InvalidFlagAccessHandler: func(_ *Context, name string) {
-			flagName = name
-		},
-	}
-
-	c := NewContext(cmd, set, nil)
-	expect(t, c.Set("missing", "") != nil, true)
-	expect(t, flagName, "missing")
-}
-
-func TestContext_LocalFlagNames(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("one-flag", false, "doc")
-	set.String("two-flag", "hello world", "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Bool("top-flag", true, "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	ctx := NewContext(nil, set, parentCtx)
-	_ = set.Parse([]string{"--one-flag", "--two-flag=foo"})
-	_ = parentSet.Parse([]string{"--top-flag"})
-
-	actualFlags := ctx.LocalFlagNames()
-	sort.Strings(actualFlags)
-
-	expect(t, actualFlags, []string{"one-flag", "two-flag"})
-}
-
-func TestContext_FlagNames(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("one-flag", false, "doc")
-	set.String("two-flag", "hello world", "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Bool("top-flag", true, "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	ctx := NewContext(nil, set, parentCtx)
-	_ = set.Parse([]string{"--one-flag", "--two-flag=foo"})
-	_ = parentSet.Parse([]string{"--top-flag"})
-
-	actualFlags := ctx.FlagNames()
-	sort.Strings(actualFlags)
-
-	expect(t, actualFlags, []string{"one-flag", "top-flag", "two-flag"})
-}
-
-func TestContext_Lineage(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("local-flag", false, "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Bool("top-flag", true, "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	ctx := NewContext(nil, set, parentCtx)
-	_ = set.Parse([]string{"--local-flag"})
-	_ = parentSet.Parse([]string{"--top-flag"})
-
-	lineage := ctx.Lineage()
-	expect(t, len(lineage), 2)
-	expect(t, lineage[0], ctx)
-	expect(t, lineage[1], parentCtx)
-}
-
-func TestContext_lookupFlagSet(t *testing.T) {
-	set := flag.NewFlagSet("test", 0)
-	set.Bool("local-flag", false, "doc")
-	parentSet := flag.NewFlagSet("test", 0)
-	parentSet.Bool("top-flag", true, "doc")
-	parentCtx := NewContext(nil, parentSet, nil)
-	ctx := NewContext(nil, set, parentCtx)
-	_ = set.Parse([]string{"--local-flag"})
-	_ = parentSet.Parse([]string{"--top-flag"})
-
-	fs := ctx.lookupFlagSet("top-flag")
-	expect(t, fs, parentCtx.flagSet)
-
-	fs = ctx.lookupFlagSet("local-flag")
-	expect(t, fs, ctx.flagSet)
-
-	if fs := ctx.lookupFlagSet("frob"); fs != nil {
-		t.Fail()
-	}
-}
-
-func TestNonNilContext(t *testing.T) {
-	ctx := NewContext(nil, nil, nil)
-	if ctx.Context == nil {
-		t.Fatal("expected a non nil context when no parent is present")
-	}
-}
-
-type testKey struct{}
-
-// TestContextPropagation tests that
-// *cli.Context always has a valid
-// context.Context
-func TestContextPropagation(t *testing.T) {
-	parent := NewContext(nil, nil, nil)
-	parent.Context = context.WithValue(context.Background(), testKey{}, "val")
-	ctx := NewContext(nil, nil, parent)
-	val := ctx.Context.Value(testKey{})
-	if val == nil {
-		t.Fatal("expected a parent context to be inherited but got nil")
-	}
-	valstr, _ := val.(string)
-	if valstr != "val" {
-		t.Fatalf("expected the context value to be %q but got %q", "val", valstr)
-	}
-	parent = NewContext(nil, nil, nil)
-	parent.Context = nil
-	ctx = NewContext(nil, nil, parent)
-	if ctx.Context == nil {
-		t.Fatal("expected context to not be nil even if the parent's context is nil")
-	}
-}
-
-func TestContextAttributeAccessing(t *testing.T) {
-	tdata := []struct {
-		testCase        string
-		setBoolInput    string
-		ctxBoolInput    string
-		newContextInput *Context
-	}{
-		{
-			testCase:        "empty",
-			setBoolInput:    "",
-			ctxBoolInput:    "",
-			newContextInput: nil,
-		},
-		{
-			testCase:        "empty_with_background_context",
-			setBoolInput:    "",
-			ctxBoolInput:    "",
-			newContextInput: &Context{Context: context.Background()},
-		},
-		{
-			testCase:        "empty_set_bool_and_present_ctx_bool",
-			setBoolInput:    "",
-			ctxBoolInput:    "ctx-bool",
-			newContextInput: nil,
-		},
-		{
-			testCase:        "present_set_bool_and_present_ctx_bool_with_background_context",
-			setBoolInput:    "",
-			ctxBoolInput:    "ctx-bool",
-			newContextInput: &Context{Context: context.Background()},
-		},
-		{
-			testCase:        "present_set_bool_and_present_ctx_bool",
-			setBoolInput:    "ctx-bool",
-			ctxBoolInput:    "ctx-bool",
-			newContextInput: nil,
-		},
-		{
-			testCase:        "present_set_bool_and_present_ctx_bool_with_background_context",
-			setBoolInput:    "ctx-bool",
-			ctxBoolInput:    "ctx-bool",
-			newContextInput: &Context{Context: context.Background()},
-		},
-		{
-			testCase:        "present_set_bool_and_different_ctx_bool",
-			setBoolInput:    "ctx-bool",
-			ctxBoolInput:    "not-ctx-bool",
-			newContextInput: nil,
-		},
-		{
-			testCase:        "present_set_bool_and_different_ctx_bool_with_background_context",
-			setBoolInput:    "ctx-bool",
-			ctxBoolInput:    "not-ctx-bool",
-			newContextInput: &Context{Context: context.Background()},
-		},
-	}
-
-	for _, test := range tdata {
-		t.Run(test.testCase, func(t *testing.T) {
-			// setup
-			set := flag.NewFlagSet("some-flag-set-name", 0)
-			set.Bool(test.setBoolInput, false, "usage documentation")
-			ctx := NewContext(nil, set, test.newContextInput)
-
-			// logic under test
-			value := ctx.Bool(test.ctxBoolInput)
-
-			// assertions
-			if value != false {
-				t.Errorf("expected \"value\" to be false, but it was not")
-			}
-		})
-	}
-}
-
-func TestCheckRequiredFlags(t *testing.T) {
-	tdata := []struct {
-		testCase              string
-		parseInput            []string
-		envVarInput           [2]string
-		flags                 []Flag
-		expectedAnError       bool
-		expectedErrorContents []string
-	}{
-		{
-			testCase: "empty",
-		},
-		{
-			testCase: "optional",
-			flags: []Flag{
-				&StringFlag{Name: "optionalFlag"},
-			},
-		},
-		{
-			testCase: "required",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true},
-			},
-			expectedAnError:       true,
-			expectedErrorContents: []string{"requiredFlag"},
-		},
-		{
-			testCase: "required_and_present",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true},
-			},
-			parseInput: []string{"--requiredFlag", "myinput"},
-		},
-		{
-			testCase: "required_and_present_via_env_var",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true, Sources: EnvVars("REQUIRED_FLAG")},
-			},
-			envVarInput: [2]string{"REQUIRED_FLAG", "true"},
-		},
-		{
-			testCase: "required_and_optional",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true},
-				&StringFlag{Name: "optionalFlag"},
-			},
-			expectedAnError: true,
-		},
-		{
-			testCase: "required_and_optional_and_optional_present",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true},
-				&StringFlag{Name: "optionalFlag"},
-			},
-			parseInput:      []string{"--optionalFlag", "myinput"},
-			expectedAnError: true,
-		},
-		{
-			testCase: "required_and_optional_and_optional_present_via_env_var",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true},
-				&StringFlag{Name: "optionalFlag", Sources: EnvVars("OPTIONAL_FLAG")},
-			},
-			envVarInput:     [2]string{"OPTIONAL_FLAG", "true"},
-			expectedAnError: true,
-		},
-		{
-			testCase: "required_and_optional_and_required_present",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true},
-				&StringFlag{Name: "optionalFlag"},
-			},
-			parseInput: []string{"--requiredFlag", "myinput"},
-		},
-		{
-			testCase: "two_required",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlagOne", Required: true},
-				&StringFlag{Name: "requiredFlagTwo", Required: true},
-			},
-			expectedAnError:       true,
-			expectedErrorContents: []string{"requiredFlagOne", "requiredFlagTwo"},
-		},
-		{
-			testCase: "two_required_and_one_present",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true},
-				&StringFlag{Name: "requiredFlagTwo", Required: true},
-			},
-			parseInput:      []string{"--requiredFlag", "myinput"},
-			expectedAnError: true,
-		},
-		{
-			testCase: "two_required_and_both_present",
-			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true},
-				&StringFlag{Name: "requiredFlagTwo", Required: true},
-			},
-			parseInput: []string{"--requiredFlag", "myinput", "--requiredFlagTwo", "myinput"},
-		},
-		{
-			testCase: "required_flag_with_short_name",
-			flags: []Flag{
-				&StringSliceFlag{Name: "names", Aliases: []string{"N"}, Required: true},
-			},
-			parseInput: []string{"-N", "asd", "-N", "qwe"},
-		},
-		{
-			testCase: "required_flag_with_multiple_short_names",
-			flags: []Flag{
-				&StringSliceFlag{Name: "names", Aliases: []string{"N", "n"}, Required: true},
-			},
-			parseInput: []string{"-n", "asd", "-n", "qwe"},
-		},
-		{
-			testCase:              "required_flag_with_short_alias_not_printed_on_error",
-			expectedAnError:       true,
-			expectedErrorContents: []string{"Required flag \"names\" not set"},
-			flags: []Flag{
-				&StringSliceFlag{Name: "names, n", Required: true},
-			},
-		},
-		{
-			testCase:              "required_flag_with_one_character",
-			expectedAnError:       true,
-			expectedErrorContents: []string{"Required flag \"n\" not set"},
-			flags: []Flag{
-				&StringFlag{Name: "n", Required: true},
-			},
-		},
-	}
-
-	for _, test := range tdata {
-		t.Run(test.testCase, func(t *testing.T) {
-			// setup
-			if test.envVarInput[0] != "" {
-				defer resetEnv(os.Environ())
-				os.Clearenv()
-				_ = os.Setenv(test.envVarInput[0], test.envVarInput[1])
-			}
-
-			set := flag.NewFlagSet("test", 0)
-			for _, flags := range test.flags {
-				_ = flags.Apply(set)
-			}
-			_ = set.Parse(test.parseInput)
-
-			c := &Context{}
-			ctx := NewContext(c.Command, set, c)
-			ctx.Command.Flags = test.flags
-
-			// logic under test
-			err := ctx.checkRequiredFlags(test.flags)
-
-			// assertions
-			if test.expectedAnError && err == nil {
-				t.Errorf("expected an error, but there was none")
-			}
-			if !test.expectedAnError && err != nil {
-				t.Errorf("did not expected an error, but there was one: %s", err)
-			}
-			for _, errString := range test.expectedErrorContents {
-				if err != nil {
-					if !strings.Contains(err.Error(), errString) {
-						t.Errorf("expected error %q to contain %q, but it didn't!", err.Error(), errString)
-					}
-				}
-			}
-		})
-	}
-}
-
-func TestContext_ParentContext_Set(t *testing.T) {
-	parentSet := flag.NewFlagSet("parent", flag.ContinueOnError)
-	parentSet.String("Name", "", "")
-
-	context := NewContext(
-		nil,
-		flag.NewFlagSet("child", flag.ContinueOnError),
-		NewContext(nil, parentSet, nil),
-	)
-
-	err := context.Set("Name", "aaa")
-	if err != nil {
-		t.Errorf("expect nil. set parent context flag return err: %s", err)
-	}
-}
diff --git a/examples_test.go b/examples_test.go
index 8503d99a89..7d668af774 100644
--- a/examples_test.go
+++ b/examples_test.go
@@ -17,8 +17,8 @@ func ExampleCommand_Run() {
 		Flags: []cli.Flag{
 			&cli.StringFlag{Name: "name", Value: "pat", Usage: "a name to say"},
 		},
-		Action: func(cCtx *cli.Context) error {
-			fmt.Printf("Hello %[1]v\n", cCtx.String("name"))
+		Action: func(_ context.Context, cmd *cli.Command) error {
+			fmt.Printf("Hello %[1]v\n", cmd.String("name"))
 			return nil
 		},
 		Authors: []any{
@@ -62,8 +62,8 @@ func ExampleCommand_Run_subcommand() {
 								Usage: "Name of the person to greet",
 							},
 						},
-						Action: func(cCtx *cli.Context) error {
-							fmt.Println("Hello,", cCtx.String("name"))
+						Action: func(_ context.Context, cmd *cli.Command) error {
+							fmt.Println("Hello,", cmd.String("name"))
 							return nil
 						},
 					},
@@ -101,7 +101,7 @@ func ExampleCommand_Run_appHelp() {
 				Aliases:     []string{"d"},
 				Usage:       "use it to see a description",
 				Description: "This is how we describe describeit the function",
-				Action: func(*cli.Context) error {
+				Action: func(context.Context, *cli.Command) error {
 					fmt.Printf("i like to describe things")
 					return nil
 				},
@@ -149,8 +149,8 @@ func ExampleCommand_Run_commandHelp() {
 		Flags: []cli.Flag{
 			&cli.StringFlag{Name: "name", Value: "pat", Usage: "a name to say"},
 		},
-		Action: func(cCtx *cli.Context) error {
-			fmt.Fprintf(cCtx.Command.Root().Writer, "hello to %[1]q\n", cCtx.String("name"))
+		Action: func(_ context.Context, cmd *cli.Command) error {
+			fmt.Fprintf(cmd.Root().Writer, "hello to %[1]q\n", cmd.String("name"))
 			return nil
 		},
 		Commands: []*cli.Command{
@@ -159,7 +159,7 @@ func ExampleCommand_Run_commandHelp() {
 				Aliases:     []string{"d"},
 				Usage:       "use it to see a description",
 				Description: "This is how we describe describeit the function",
-				Action: func(*cli.Context) error {
+				Action: func(context.Context, *cli.Command) error {
 					fmt.Println("i like to describe things")
 					return nil
 				},
@@ -347,7 +347,7 @@ func ExampleCommand_Run_shellComplete_bash() {
 				Aliases:     []string{"d"},
 				Usage:       "use it to see a description",
 				Description: "This is how we describe describeit the function",
-				Action: func(*cli.Context) error {
+				Action: func(context.Context, *cli.Command) error {
 					fmt.Printf("i like to describe things")
 					return nil
 				},
@@ -355,7 +355,7 @@ func ExampleCommand_Run_shellComplete_bash() {
 				Name:        "next",
 				Usage:       "next example",
 				Description: "more stuff to see when generating shell completion",
-				Action: func(*cli.Context) error {
+				Action: func(context.Context, *cli.Command) error {
 					fmt.Printf("the next example")
 					return nil
 				},
@@ -386,7 +386,7 @@ func ExampleCommand_Run_shellComplete_zsh() {
 				Aliases:     []string{"d"},
 				Usage:       "use it to see a description",
 				Description: "This is how we describe describeit the function",
-				Action: func(*cli.Context) error {
+				Action: func(context.Context, *cli.Command) error {
 					fmt.Printf("i like to describe things")
 					return nil
 				},
@@ -394,7 +394,7 @@ func ExampleCommand_Run_shellComplete_zsh() {
 				Name:        "next",
 				Usage:       "next example",
 				Description: "more stuff to see when generating bash completion",
-				Action: func(*cli.Context) error {
+				Action: func(context.Context, *cli.Command) error {
 					fmt.Printf("the next example")
 					return nil
 				},
@@ -423,11 +423,11 @@ func ExampleCommand_Run_sliceValues() {
 			&cli.FloatSliceFlag{Name: "float64Slice"},
 			&cli.IntSliceFlag{Name: "intSlice"},
 		},
-		Action: func(cCtx *cli.Context) error {
-			for i, v := range cCtx.FlagNames() {
-				fmt.Printf("%d-%s %#v\n", i, v, cCtx.Value(v))
+		Action: func(ctx context.Context, cmd *cli.Command) error {
+			for i, v := range cmd.FlagNames() {
+				fmt.Printf("%d-%s %#v\n", i, v, cmd.Value(v))
 			}
-			err := cCtx.Err()
+			err := ctx.Err()
 			fmt.Println("error:", err)
 			return err
 		},
@@ -455,12 +455,12 @@ func ExampleCommand_Run_mapValues() {
 		Flags: []cli.Flag{
 			&cli.StringMapFlag{Name: "stringMap"},
 		},
-		Action: func(cCtx *cli.Context) error {
-			for i, v := range cCtx.FlagNames() {
-				fmt.Printf("%d-%s %#v\n", i, v, cCtx.StringMap(v))
+		Action: func(ctx context.Context, cmd *cli.Command) error {
+			for i, v := range cmd.FlagNames() {
+				fmt.Printf("%d-%s %#v\n", i, v, cmd.StringMap(v))
 			}
-			fmt.Printf("notfound %#v\n", cCtx.StringMap("notfound"))
-			err := cCtx.Err()
+			fmt.Printf("notfound %#v\n", cmd.StringMap("notfound"))
+			err := ctx.Err()
 			fmt.Println("error:", err)
 			return err
 		},
@@ -490,7 +490,7 @@ func ExampleBoolWithInverseFlag() {
 		Flags: []cli.Flag{
 			flagWithInverse,
 		},
-		Action: func(ctx *cli.Context) error {
+		Action: func(_ context.Context, cmd *cli.Command) error {
 			if flagWithInverse.IsSet() {
 				if flagWithInverse.Value() {
 					fmt.Println("env is set")
@@ -525,8 +525,8 @@ func ExampleCommand_Suggest() {
 		Flags: []cli.Flag{
 			&cli.StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"},
 		},
-		Action: func(cCtx *cli.Context) error {
-			fmt.Printf("Hello %v\n", cCtx.String("name"))
+		Action: func(_ context.Context, cmd *cli.Command) error {
+			fmt.Printf("Hello %v\n", cmd.String("name"))
 			return nil
 		},
 	}
@@ -549,8 +549,8 @@ func ExampleCommand_Suggest_command() {
 		Flags: []cli.Flag{
 			&cli.StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"},
 		},
-		Action: func(cCtx *cli.Context) error {
-			fmt.Printf("Hello %v\n", cCtx.String("name"))
+		Action: func(_ context.Context, cmd *cli.Command) error {
+			fmt.Printf("Hello %v\n", cmd.String("name"))
 			return nil
 		},
 		Commands: []*cli.Command{
@@ -563,8 +563,8 @@ func ExampleCommand_Suggest_command() {
 				Flags: []cli.Flag{
 					&cli.BoolFlag{Name: "smiling"},
 				},
-				Action: func(cCtx *cli.Context) error {
-					if cCtx.Bool("smiling") {
+				Action: func(_ context.Context, cmd *cli.Command) error {
+					if cmd.Bool("smiling") {
 						fmt.Println("😀")
 					}
 					fmt.Println("Hello, neighbors")
diff --git a/flag.go b/flag.go
index 1ef0c10b03..5332b22201 100644
--- a/flag.go
+++ b/flag.go
@@ -1,6 +1,7 @@
 package cli
 
 import (
+	"context"
 	"errors"
 	"flag"
 	"fmt"
@@ -91,7 +92,7 @@ func (f FlagsByName) Swap(i, j int) {
 
 // ActionableFlag is an interface that wraps Flag interface and RunAction operation.
 type ActionableFlag interface {
-	RunAction(*Context) error
+	RunAction(context.Context, *Command) error
 }
 
 // Flag is a common interface related to parsing flags in cli.
@@ -169,7 +170,7 @@ type PersistentFlag interface {
 	IsPersistent() bool
 }
 
-func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
+func newFlagSet(name string, flags []Flag) (*flag.FlagSet, error) {
 	set := flag.NewFlagSet(name, flag.ContinueOnError)
 
 	for _, f := range flags {
@@ -177,7 +178,9 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
 			return nil, err
 		}
 	}
+
 	set.SetOutput(io.Discard)
+
 	return set, nil
 }
 
diff --git a/flag_bool.go b/flag_bool.go
index 8a25de7662..fd7f8c476c 100644
--- a/flag_bool.go
+++ b/flag_bool.go
@@ -24,10 +24,13 @@ type boolValue struct {
 	count       *int
 }
 
-func (cCtx *Context) Bool(name string) bool {
-	if v, ok := cCtx.Value(name).(bool); ok {
+func (cmd *Command) Bool(name string) bool {
+	if v, ok := cmd.Value(name).(bool); ok {
+		tracef("bool available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return false
 }
 
diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go
index f19c021ed3..7b6e18c7a7 100644
--- a/flag_bool_with_inverse.go
+++ b/flag_bool_with_inverse.go
@@ -1,6 +1,7 @@
 package cli
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"strings"
@@ -40,20 +41,20 @@ func (s *BoolWithInverseFlag) Value() bool {
 	return *s.posDest
 }
 
-func (s *BoolWithInverseFlag) RunAction(ctx *Context) error {
+func (s *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error {
 	if *s.negDest && *s.posDest {
 		return fmt.Errorf("cannot set both flags `--%s` and `--%s`", s.positiveFlag.Name, s.negativeFlag.Name)
 	}
 
 	if *s.negDest {
-		err := ctx.Set(s.positiveFlag.Name, "false")
+		err := cmd.Set(s.positiveFlag.Name, "false")
 		if err != nil {
 			return err
 		}
 	}
 
 	if s.BoolFlag.Action != nil {
-		return s.BoolFlag.Action(ctx, s.Value())
+		return s.BoolFlag.Action(ctx, cmd, s.Value())
 	}
 
 	return nil
diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go
index 12853e5589..8f6b94af1f 100644
--- a/flag_bool_with_inverse_test.go
+++ b/flag_bool_with_inverse_test.go
@@ -1,6 +1,7 @@
 package cli
 
 import (
+	"context"
 	"fmt"
 	"strings"
 	"testing"
@@ -23,7 +24,7 @@ type boolWithInverseTestCase struct {
 func (tc *boolWithInverseTestCase) Run(t *testing.T, flagWithInverse *BoolWithInverseFlag) error {
 	cmd := &Command{
 		Flags:  []Flag{flagWithInverse},
-		Action: func(ctx *Context) error { return nil },
+		Action: func(context.Context, *Command) error { return nil },
 	}
 
 	for key, val := range tc.envVars {
@@ -115,12 +116,12 @@ func TestBoolWithInverseAction(t *testing.T) {
 				Name: "env",
 
 				// Setting env to the opposite to test flag Action is working as intended
-				Action: func(ctx *Context, value bool) error {
+				Action: func(_ context.Context, cmd *Command, value bool) error {
 					if value {
-						return ctx.Set("env", "false")
+						return cmd.Set("env", "false")
 					}
 
-					return ctx.Set("env", "true")
+					return cmd.Set("env", "true")
 				},
 			},
 		}
diff --git a/flag_duration.go b/flag_duration.go
index ebcc86b896..2a79458c28 100644
--- a/flag_duration.go
+++ b/flag_duration.go
@@ -36,9 +36,12 @@ func (d *durationValue) Get() any { return time.Duration(*d) }
 
 func (d *durationValue) String() string { return (*time.Duration)(d).String() }
 
-func (cCtx *Context) Duration(name string) time.Duration {
-	if v, ok := cCtx.Value(name).(time.Duration); ok {
+func (cmd *Command) Duration(name string) time.Duration {
+	if v, ok := cmd.Value(name).(time.Duration); ok {
+		tracef("duration available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return 0
 }
diff --git a/flag_float.go b/flag_float.go
index 9e0a1f85f3..058440fccc 100644
--- a/flag_float.go
+++ b/flag_float.go
@@ -37,9 +37,12 @@ func (f *floatValue) String() string { return strconv.FormatFloat(float64(*f), '
 
 // Int looks up the value of a local IntFlag, returns
 // 0 if not found
-func (cCtx *Context) Float(name string) float64 {
-	if v, ok := cCtx.Value(name).(float64); ok {
+func (cmd *Command) Float(name string) float64 {
+	if v, ok := cmd.Value(name).(float64); ok {
+		tracef("float available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("float NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return 0
 }
diff --git a/flag_float_slice.go b/flag_float_slice.go
index 521da504fb..837e911da8 100644
--- a/flag_float_slice.go
+++ b/flag_float_slice.go
@@ -11,19 +11,23 @@ var NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue]
 
 // FloatSlice looks up the value of a local FloatSliceFlag, returns
 // nil if not found
-func (cCtx *Context) FloatSlice(name string) []float64 {
-	if fs := cCtx.lookupFlagSet(name); fs != nil {
-		return lookupFloatSlice(name, fs)
+func (cmd *Command) FloatSlice(name string) []float64 {
+	if flSet := cmd.lookupFlagSet(name); flSet != nil {
+		return lookupFloatSlice(name, flSet, cmd.Name)
 	}
+
 	return nil
 }
 
-func lookupFloatSlice(name string, set *flag.FlagSet) []float64 {
-	f := set.Lookup(name)
-	if f != nil {
-		if slice, ok := f.Value.(flag.Getter).Get().([]float64); ok {
-			return slice
+func lookupFloatSlice(name string, set *flag.FlagSet, cmdName string) []float64 {
+	fl := set.Lookup(name)
+	if fl != nil {
+		if v, ok := fl.Value.(flag.Getter).Get().([]float64); ok {
+			tracef("float slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmdName)
+			return v
 		}
 	}
+
+	tracef("float slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmdName)
 	return nil
 }
diff --git a/flag_impl.go b/flag_impl.go
index 257549ba3f..e4930195b9 100644
--- a/flag_impl.go
+++ b/flag_impl.go
@@ -1,6 +1,7 @@
 package cli
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"reflect"
@@ -87,7 +88,7 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 
 	TakesFile bool // whether this flag takes a file argument, mainly for shell completion purposes
 
-	Action func(*Context, T) error // Action callback to be called when flag is set
+	Action func(context.Context, *Command, T) error // Action callback to be called when flag is set
 
 	Config C // Additional/Custom configuration associated with this flag type
 
@@ -262,9 +263,9 @@ func (f *FlagBase[T, C, V]) GetDefaultText() string {
 	return v.ToString(f.Value)
 }
 
-// Get returns the flag’s value in the given Context.
-func (f *FlagBase[T, C, V]) Get(ctx *Context) T {
-	if v, ok := ctx.Value(f.Name).(T); ok {
+// Get returns the flag’s value in the given Command.
+func (f *FlagBase[T, C, V]) Get(cmd *Command) T {
+	if v, ok := cmd.Value(f.Name).(T); ok {
 		return v
 	}
 	var t T
@@ -272,9 +273,9 @@ func (f *FlagBase[T, C, V]) Get(ctx *Context) T {
 }
 
 // RunAction executes flag action if set
-func (f *FlagBase[T, C, V]) RunAction(ctx *Context) error {
+func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
 	if f.Action != nil {
-		return f.Action(ctx, f.Get(ctx))
+		return f.Action(ctx, cmd, f.Get(cmd))
 	}
 
 	return nil
diff --git a/flag_int.go b/flag_int.go
index d7398f92bf..4403dae0f2 100644
--- a/flag_int.go
+++ b/flag_int.go
@@ -48,9 +48,12 @@ func (i *intValue) String() string { return strconv.FormatInt(int64(*i.val), 10)
 
 // Int64 looks up the value of a local Int64Flag, returns
 // 0 if not found
-func (cCtx *Context) Int(name string) int64 {
-	if v, ok := cCtx.Value(name).(int64); ok {
+func (cmd *Command) Int(name string) int64 {
+	if v, ok := cmd.Value(name).(int64); ok {
+		tracef("int available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("int NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return 0
 }
diff --git a/flag_int_slice.go b/flag_int_slice.go
index 6146e937bc..24bb2616eb 100644
--- a/flag_int_slice.go
+++ b/flag_int_slice.go
@@ -7,10 +7,12 @@ var NewIntSlice = NewSliceBase[int64, IntegerConfig, intValue]
 
 // IntSlice looks up the value of a local IntSliceFlag, returns
 // nil if not found
-func (cCtx *Context) IntSlice(name string) []int64 {
-	if v, ok := cCtx.Value(name).([]int64); ok {
+func (cmd *Command) IntSlice(name string) []int64 {
+	if v, ok := cmd.Value(name).([]int64); ok {
+		tracef("int slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
 
+	tracef("int slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return nil
 }
diff --git a/flag_mutex.go b/flag_mutex.go
index 9417da85d4..2ca38e71ce 100644
--- a/flag_mutex.go
+++ b/flag_mutex.go
@@ -13,14 +13,14 @@ type MutuallyExclusiveFlags struct {
 	Required bool
 }
 
-func (grp MutuallyExclusiveFlags) check(ctx *Context) error {
+func (grp MutuallyExclusiveFlags) check(cmd *Command) error {
 	oneSet := false
 	e := &mutuallyExclusiveGroup{}
 
 	for _, grpf := range grp.Flags {
 		for _, f := range grpf {
 			for _, name := range f.Names() {
-				if ctx.IsSet(name) {
+				if cmd.IsSet(name) {
 					if oneSet {
 						e.flag2Name = name
 						return e
diff --git a/flag_string.go b/flag_string.go
index 1bc205cfb1..c56c99b5af 100644
--- a/flag_string.go
+++ b/flag_string.go
@@ -55,9 +55,12 @@ func (s *stringValue) String() string {
 	return ""
 }
 
-func (cCtx *Context) String(name string) string {
-	if v, ok := cCtx.Value(name).(string); ok {
+func (cmd *Command) String(name string) string {
+	if v, ok := cmd.Value(name).(string); ok {
+		tracef("string available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("string NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return ""
 }
diff --git a/flag_string_map.go b/flag_string_map.go
index 58f07f7965..62dfb34c32 100644
--- a/flag_string_map.go
+++ b/flag_string_map.go
@@ -7,9 +7,12 @@ var NewStringMap = NewMapBase[string, StringConfig, stringValue]
 
 // StringMap looks up the value of a local StringMapFlag, returns
 // nil if not found
-func (cCtx *Context) StringMap(name string) map[string]string {
-	if v, ok := cCtx.Value(name).(map[string]string); ok {
+func (cmd *Command) StringMap(name string) map[string]string {
+	if v, ok := cmd.Value(name).(map[string]string); ok {
+		tracef("string map available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("string map NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return nil
 }
diff --git a/flag_string_slice.go b/flag_string_slice.go
index 89370fa795..d2ae095d84 100644
--- a/flag_string_slice.go
+++ b/flag_string_slice.go
@@ -7,9 +7,12 @@ var NewStringSlice = NewSliceBase[string, StringConfig, stringValue]
 
 // StringSlice looks up the value of a local StringSliceFlag, returns
 // nil if not found
-func (cCtx *Context) StringSlice(name string) []string {
-	if v, ok := cCtx.Value(name).([]string); ok {
+func (cmd *Command) StringSlice(name string) []string {
+	if v, ok := cmd.Value(name).([]string); ok {
+		tracef("string slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("string slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return nil
 }
diff --git a/flag_test.go b/flag_test.go
index 1f15b40bc3..d973c471de 100644
--- a/flag_test.go
+++ b/flag_test.go
@@ -1,6 +1,7 @@
 package cli
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"io"
@@ -50,15 +51,17 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) {
 	expect(t, v, true)
 }
 
-func TestBoolFlagValueFromContext(t *testing.T) {
+func TestBoolFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Bool("trueflag", true, "doc")
 	set.Bool("falseflag", false, "doc")
-	cCtx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	tf := &BoolFlag{Name: "trueflag"}
 	ff := &BoolFlag{Name: "falseflag"}
-	expect(t, tf.Get(cCtx), true)
-	expect(t, ff.Get(cCtx), false)
+
+	r := require.New(t)
+	r.True(tf.Get(cmd))
+	r.False(ff.Get(cmd))
 }
 
 func TestBoolFlagApply_SetsCount(t *testing.T) {
@@ -75,7 +78,7 @@ func TestBoolFlagApply_SetsCount(t *testing.T) {
 	expect(t, count, 3)
 }
 
-func TestBoolFlagCountFromContext(t *testing.T) {
+func TestBoolFlagCountFromCommand(t *testing.T) {
 	boolCountTests := []struct {
 		input         []string
 		expectedVal   bool
@@ -95,15 +98,15 @@ func TestBoolFlagCountFromContext(t *testing.T) {
 
 	for _, bct := range boolCountTests {
 		set := flag.NewFlagSet("test", 0)
-		ctx := NewContext(nil, set, nil)
+		cmd := &Command{flagSet: set}
 		tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}}
-		err := tf.Apply(set)
-		expect(t, err, nil)
+		r := require.New(t)
 
-		err = set.Parse(bct.input)
-		expect(t, err, nil)
-		expect(t, tf.Get(ctx), bct.expectedVal)
-		expect(t, ctx.Count("tf"), bct.expectedCount)
+		r.NoError(tf.Apply(set))
+		r.NoError(set.Parse(bct.input))
+
+		r.Equal(bct.expectedVal, tf.Get(cmd))
+		r.Equal(bct.expectedCount, cmd.Count("tf"))
 	}
 }
 
@@ -371,10 +374,10 @@ func TestFlagsFromEnv(t *testing.T) {
 
 			cmd := &Command{
 				Flags: []Flag{tc.fl},
-				Action: func(ctx *Context) error {
-					r.Equal(ctx.Value(tc.fl.Names()[0]), tc.output)
+				Action: func(_ context.Context, cmd *Command) error {
+					r.Equal(cmd.Value(tc.fl.Names()[0]), tc.output)
 					r.True(tc.fl.IsSet())
-					r.Equal(ctx.FlagNames(), tc.fl.Names())
+					r.Equal(cmd.FlagNames(), tc.fl.Names())
 
 					return nil
 				},
@@ -658,12 +661,12 @@ func TestStringFlagApply_SetsAllNames(t *testing.T) {
 	expect(t, v, "YUUUU")
 }
 
-func TestStringFlagValueFromContext(t *testing.T) {
+func TestStringFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.String("myflag", "foobar", "doc")
-	ctx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &StringFlag{Name: "myflag"}
-	expect(t, f.Get(ctx), "foobar")
+	require.Equal(t, "foobar", f.Get(cmd))
 }
 
 var _ = []struct {
@@ -789,12 +792,12 @@ func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
 	expect(t, defValue, dest)
 }
 
-func TestStringSliceFlagValueFromContext(t *testing.T) {
+func TestStringSliceFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Var(NewStringSlice("a", "b", "c"), "myflag", "doc")
-	ctx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &StringSliceFlag{Name: "myflag"}
-	expect(t, f.Get(ctx), []string{"a", "b", "c"})
+	require.Equal(t, []string{"a", "b", "c"}, f.Get(cmd))
 }
 
 var intFlagTests = []struct {
@@ -851,12 +854,12 @@ func TestIntFlagApply_SetsAllNames(t *testing.T) {
 	r.Equal(int64(5), v)
 }
 
-func TestIntFlagValueFromContext(t *testing.T) {
+func TestIntFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Int64("myflag", int64(42), "doc")
-	cCtx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	fl := &IntFlag{Name: "myflag"}
-	require.Equal(t, int64(42), fl.Get(cCtx))
+	require.Equal(t, int64(42), fl.Get(cmd))
 }
 
 var uintFlagTests = []struct {
@@ -902,12 +905,12 @@ func TestUintFlagWithEnvVarHelpOutput(t *testing.T) {
 	}
 }
 
-func TestUintFlagValueFromContext(t *testing.T) {
+func TestUintFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Uint64("myflag", 42, "doc")
-	cCtx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	fl := &UintFlag{Name: "myflag"}
-	require.Equal(t, uint64(42), fl.Get(cCtx))
+	require.Equal(t, uint64(42), fl.Get(cmd))
 }
 
 var uint64FlagTests = []struct {
@@ -953,12 +956,12 @@ func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) {
 	}
 }
 
-func TestUint64FlagValueFromContext(t *testing.T) {
+func TestUint64FlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Uint64("myflag", 42, "doc")
-	ctx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &UintFlag{Name: "myflag"}
-	expect(t, f.Get(ctx), uint64(42))
+	require.Equal(t, uint64(42), f.Get(cmd))
 }
 
 var durationFlagTests = []struct {
@@ -1015,12 +1018,12 @@ func TestDurationFlagApply_SetsAllNames(t *testing.T) {
 	expect(t, v, time.Hour*30)
 }
 
-func TestDurationFlagValueFromContext(t *testing.T) {
+func TestDurationFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Duration("myflag", 42*time.Second, "doc")
-	ctx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &DurationFlag{Name: "myflag"}
-	expect(t, f.Get(ctx), 42*time.Second)
+	require.Equal(t, 42*time.Second, f.Get(cmd))
 }
 
 var intSliceFlagTests = []struct {
@@ -1116,8 +1119,8 @@ func TestIntSliceFlagApply_ParentContext(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "child",
-				Action: func(ctx *Context) error {
-					require.Equalf(t, []int64{1, 2, 3}, ctx.IntSlice("numbers"), "child context unable to view parent flag")
+				Action: func(_ context.Context, cmd *Command) error {
+					require.Equalf(t, []int64{1, 2, 3}, cmd.IntSlice("numbers"), "child context unable to view parent flag")
 
 					return nil
 				},
@@ -1126,26 +1129,26 @@ func TestIntSliceFlagApply_ParentContext(t *testing.T) {
 	}).Run(buildTestContext(t), []string{"run", "child"})
 }
 
-func TestIntSliceFlag_SetFromParentContext(t *testing.T) {
+func TestIntSliceFlag_SetFromParentCommand(t *testing.T) {
 	fl := &IntSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: []int64{1, 2, 3, 4}}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
-	cCtx := &Context{
-		parent: &Context{
+	cmd := &Command{
+		parent: &Command{
 			flagSet: set,
 		},
 		flagSet: flag.NewFlagSet("empty", 0),
 	}
 
-	require.Equalf(t, []int64{1, 2, 3, 4}, cCtx.IntSlice("numbers"), "child context unable to view parent flag")
+	require.Equalf(t, []int64{1, 2, 3, 4}, cmd.IntSlice("numbers"), "child context unable to view parent flag")
 }
 
-func TestIntSliceFlagValueFromContext(t *testing.T) {
+func TestIntSliceFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Var(NewIntSlice(1, 2, 3), "myflag", "doc")
-	cCtx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &IntSliceFlag{Name: "myflag"}
-	require.Equal(t, f.Get(cCtx), []int64{1, 2, 3})
+	require.Equal(t, []int64{1, 2, 3}, f.Get(cmd))
 }
 
 var uintSliceFlagTests = []struct {
@@ -1243,9 +1246,9 @@ func TestUintSliceFlagApply_ParentContext(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "child",
-				Action: func(ctx *Context) error {
+				Action: func(_ context.Context, cmd *Command) error {
 					require.Equalf(
-						t, []uint64{1, 2, 3}, ctx.UintSlice("numbers"),
+						t, []uint64{1, 2, 3}, cmd.UintSlice("numbers"),
 						"child context unable to view parent flag",
 					)
 					return nil
@@ -1255,14 +1258,14 @@ func TestUintSliceFlagApply_ParentContext(t *testing.T) {
 	}).Run(buildTestContext(t), []string{"run", "child"})
 }
 
-func TestUintSliceFlag_SetFromParentContext(t *testing.T) {
+func TestUintSliceFlag_SetFromParentCommand(t *testing.T) {
 	fl := &UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: []uint64{1, 2, 3, 4}}
 	set := flag.NewFlagSet("test", 0)
 	r := require.New(t)
 	r.NoError(fl.Apply(set))
 
-	cCtx := &Context{
-		parent: &Context{
+	cmd := &Command{
+		parent: &Command{
 			flagSet: set,
 		},
 		flagSet: flag.NewFlagSet("empty", 0),
@@ -1270,7 +1273,7 @@ func TestUintSliceFlag_SetFromParentContext(t *testing.T) {
 
 	r.Equalf(
 		[]uint64{1, 2, 3, 4},
-		cCtx.UintSlice("numbers"),
+		cmd.UintSlice("numbers"),
 		"child context unable to view parent flag",
 	)
 }
@@ -1280,14 +1283,15 @@ func TestUintSliceFlag_ReturnNil(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	r := require.New(t)
 	r.NoError(fl.Apply(set))
-	cCtx := &Context{
-		parent: &Context{
+	cmd := &Command{
+		parent: &Command{
 			flagSet: set,
 		},
 		flagSet: flag.NewFlagSet("empty", 0),
 	}
 	r.Equalf(
-		[]uint64(nil), cCtx.UintSlice("numbers"),
+		[]uint64(nil),
+		cmd.UintSlice("numbers"),
 		"child context unable to view parent flag",
 	)
 }
@@ -1383,7 +1387,7 @@ func TestUint64SliceFlagApply_DefaultValueWithDestination(t *testing.T) {
 	expect(t, defValue, dest)
 }
 
-func TestUint64SliceFlagApply_ParentContext(t *testing.T) {
+func TestUint64SliceFlagApply_ParentCommand(t *testing.T) {
 	_ = (&Command{
 		Flags: []Flag{
 			&UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: []uint64{1, 2, 3}},
@@ -1391,9 +1395,9 @@ func TestUint64SliceFlagApply_ParentContext(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "child",
-				Action: func(ctx *Context) error {
+				Action: func(_ context.Context, cmd *Command) error {
 					require.Equalf(
-						t, []uint64{1, 2, 3}, ctx.UintSlice("numbers"),
+						t, []uint64{1, 2, 3}, cmd.UintSlice("numbers"),
 						"child context unable to view parent flag",
 					)
 					return nil
@@ -1403,19 +1407,19 @@ func TestUint64SliceFlagApply_ParentContext(t *testing.T) {
 	}).Run(buildTestContext(t), []string{"run", "child"})
 }
 
-func TestUint64SliceFlag_SetFromParentContext(t *testing.T) {
+func TestUint64SliceFlag_SetFromParentCommand(t *testing.T) {
 	fl := &UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: []uint64{1, 2, 3, 4}}
 	set := flag.NewFlagSet("test", 0)
 	r := require.New(t)
 	r.NoError(fl.Apply(set))
-	cCtx := &Context{
-		parent: &Context{
+	cmd := &Command{
+		parent: &Command{
 			flagSet: set,
 		},
 		flagSet: flag.NewFlagSet("empty", 0),
 	}
 	r.Equalf(
-		[]uint64{1, 2, 3, 4}, cCtx.UintSlice("numbers"),
+		[]uint64{1, 2, 3, 4}, cmd.UintSlice("numbers"),
 		"child context unable to view parent flag",
 	)
 }
@@ -1425,14 +1429,14 @@ func TestUint64SliceFlag_ReturnNil(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	r := require.New(t)
 	r.NoError(fl.Apply(set))
-	cCtx := &Context{
-		parent: &Context{
+	cmd := &Command{
+		parent: &Command{
 			flagSet: set,
 		},
 		flagSet: flag.NewFlagSet("empty", 0),
 	}
 	r.Equalf(
-		[]uint64(nil), cCtx.UintSlice("numbers"),
+		[]uint64(nil), cmd.UintSlice("numbers"),
 		"child context unable to view parent flag",
 	)
 }
@@ -1483,12 +1487,12 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) {
 	expect(t, v, float64(43.33333))
 }
 
-func TestFloat64FlagValueFromContext(t *testing.T) {
+func TestFloat64FlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Float64("myflag", 1.23, "doc")
-	ctx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &FloatFlag{Name: "myflag"}
-	expect(t, f.Get(ctx), 1.23)
+	require.Equal(t, 1.23, f.Get(cmd))
 }
 
 var float64SliceFlagTests = []struct {
@@ -1582,15 +1586,15 @@ func TestFloat64SliceFlagApply_DefaultValueWithDestination(t *testing.T) {
 	expect(t, defValue, dest)
 }
 
-func TestFloat64SliceFlagValueFromContext(t *testing.T) {
+func TestFloat64SliceFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Var(NewFloatSlice(1.23, 4.56), "myflag", "doc")
-	ctx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &FloatSliceFlag{Name: "myflag"}
-	expect(t, f.Get(ctx), []float64{1.23, 4.56})
+	require.Equal(t, []float64{1.23, 4.56}, f.Get(cmd))
 }
 
-func TestFloat64SliceFlagApply_ParentContext(t *testing.T) {
+func TestFloat64SliceFlagApply_ParentCommand(t *testing.T) {
 	_ = (&Command{
 		Flags: []Flag{
 			&FloatSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: []float64{1.0, 2.0, 3.0}},
@@ -1598,8 +1602,8 @@ func TestFloat64SliceFlagApply_ParentContext(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "child",
-				Action: func(ctx *Context) error {
-					require.Equalf(t, []float64{1.0, 2.0, 3.0}, ctx.FloatSlice("numbers"), "child context unable to view parent flag")
+				Action: func(_ context.Context, cmd *Command) error {
+					require.Equalf(t, []float64{1.0, 2.0, 3.0}, cmd.FloatSlice("numbers"), "child context unable to view parent flag")
 					return nil
 				},
 			},
@@ -1612,11 +1616,11 @@ func TestParseMultiString(t *testing.T) {
 		Flags: []Flag{
 			&StringFlag{Name: "serve", Aliases: []string{"s"}},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.String("serve") != "10" {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.String("serve") != "10" {
 				t.Errorf("main name not set")
 			}
-			if ctx.String("s") != "10" {
+			if cmd.String("s") != "10" {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -1633,7 +1637,7 @@ func TestParseDestinationString(t *testing.T) {
 				Destination: &dest,
 			},
 		},
-		Action: func(*Context) error {
+		Action: func(context.Context, *Command) error {
 			if dest != "10" {
 				t.Errorf("expected destination String 10")
 			}
@@ -1649,11 +1653,11 @@ func TestParseMultiStringFromEnv(t *testing.T) {
 		Flags: []Flag{
 			&StringFlag{Name: "count", Aliases: []string{"c"}, Sources: EnvVars("APP_COUNT")},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.String("count") != "20" {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.String("count") != "20" {
 				t.Errorf("main name not set")
 			}
-			if ctx.String("c") != "20" {
+			if cmd.String("c") != "20" {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -1662,19 +1666,17 @@ func TestParseMultiStringFromEnv(t *testing.T) {
 }
 
 func TestParseMultiStringFromEnvCascade(t *testing.T) {
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_COUNT", "20")
+	t.Setenv("APP_COUNT", "20")
 
 	_ = (&Command{
 		Flags: []Flag{
 			&StringFlag{Name: "count", Aliases: []string{"c"}, Sources: EnvVars("COMPAT_COUNT", "APP_COUNT")},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.String("count") != "20" {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.String("count") != "20" {
 				t.Errorf("main name not set")
 			}
-			if ctx.String("c") != "20" {
+			if cmd.String("c") != "20" {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -1687,13 +1689,13 @@ func TestParseMultiStringSlice(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: []string{}},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			expected := []string{"10", "20"}
-			if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) {
-				t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve"))
+			if !reflect.DeepEqual(cmd.StringSlice("serve"), expected) {
+				t.Errorf("main name not set: %v != %v", expected, cmd.StringSlice("serve"))
 			}
-			if !reflect.DeepEqual(ctx.StringSlice("s"), expected) {
-				t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s"))
+			if !reflect.DeepEqual(cmd.StringSlice("s"), expected) {
+				t.Errorf("short name not set: %v != %v", expected, cmd.StringSlice("s"))
 			}
 			return nil
 		},
@@ -1705,13 +1707,13 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: []string{"9", "2"}},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			expected := []string{"10", "20"}
-			if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) {
-				t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve"))
+			if !reflect.DeepEqual(cmd.StringSlice("serve"), expected) {
+				t.Errorf("main name not set: %v != %v", expected, cmd.StringSlice("serve"))
 			}
-			if !reflect.DeepEqual(ctx.StringSlice("s"), expected) {
-				t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s"))
+			if !reflect.DeepEqual(cmd.StringSlice("s"), expected) {
+				t.Errorf("short name not set: %v != %v", expected, cmd.StringSlice("s"))
 			}
 			return nil
 		},
@@ -1725,13 +1727,13 @@ func TestParseMultiStringSliceWithDestination(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			expected := []string{"10", "20"}
 			if !reflect.DeepEqual(dest, expected) {
-				t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve"))
+				t.Errorf("main name not set: %v != %v", expected, cmd.StringSlice("serve"))
 			}
 			if !reflect.DeepEqual(dest, expected) {
-				t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s"))
+				t.Errorf("short name not set: %v != %v", expected, cmd.StringSlice("s"))
 			}
 			return nil
 		},
@@ -1746,13 +1748,13 @@ func TestParseMultiStringSliceWithDestinationAndEnv(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			expected := []string{"10", "20"}
 			if !reflect.DeepEqual(dest, expected) {
-				t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve"))
+				t.Errorf("main name not set: %v != %v", expected, cmd.StringSlice("serve"))
 			}
 			if !reflect.DeepEqual(dest, expected) {
-				t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s"))
+				t.Errorf("short name not set: %v != %v", expected, cmd.StringSlice("s"))
 			}
 			return nil
 		},
@@ -1769,13 +1771,13 @@ func TestParseMultiFloat64SliceWithDestinationAndEnv(t *testing.T) {
 		Flags: []Flag{
 			&FloatSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			expected := []float64{10, 20}
 			if !reflect.DeepEqual(dest, expected) {
-				t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve"))
+				t.Errorf("main name not set: %v != %v", expected, cmd.StringSlice("serve"))
 			}
 			if !reflect.DeepEqual(dest, expected) {
-				t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s"))
+				t.Errorf("short name not set: %v != %v", expected, cmd.StringSlice("s"))
 			}
 			return nil
 		},
@@ -1790,7 +1792,7 @@ func TestParseMultiIntSliceWithDestinationAndEnv(t *testing.T) {
 		Flags: []Flag{
 			&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(context.Context, *Command) error {
 			require.Equalf(t, []int64{10, 20}, dest, "main name not set")
 
 			return nil
@@ -1803,12 +1805,12 @@ func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: []string{"9", "2"}},
 		},
-		Action: func(ctx *Context) error {
-			if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) {
-				t.Errorf("main name not set: %v", ctx.StringSlice("serve"))
+		Action: func(_ context.Context, cmd *Command) error {
+			if !reflect.DeepEqual(cmd.StringSlice("serve"), []string{"9", "2"}) {
+				t.Errorf("main name not set: %v", cmd.StringSlice("serve"))
 			}
-			if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"9", "2"}) {
-				t.Errorf("short name not set: %v", ctx.StringSlice("s"))
+			if !reflect.DeepEqual(cmd.StringSlice("s"), []string{"9", "2"}) {
+				t.Errorf("short name not set: %v", cmd.StringSlice("s"))
 			}
 			return nil
 		},
@@ -1824,11 +1826,11 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{}, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
-			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
+		Action: func(_ context.Context, cmd *Command) error {
+			if !reflect.DeepEqual(cmd.StringSlice("intervals"), []string{"20", "30", "40"}) {
 				t.Errorf("main name not set from env")
 			}
-			if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
+			if !reflect.DeepEqual(cmd.StringSlice("i"), []string{"20", "30", "40"}) {
 				t.Errorf("short name not set from env")
 			}
 			return nil
@@ -1845,11 +1847,11 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{"1", "2", "5"}, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
-			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
+		Action: func(_ context.Context, cmd *Command) error {
+			if !reflect.DeepEqual(cmd.StringSlice("intervals"), []string{"20", "30", "40"}) {
 				t.Errorf("main name not set from env")
 			}
-			if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
+			if !reflect.DeepEqual(cmd.StringSlice("i"), []string{"20", "30", "40"}) {
 				t.Errorf("short name not set from env")
 			}
 			return nil
@@ -1866,11 +1868,11 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
-			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
+		Action: func(_ context.Context, cmd *Command) error {
+			if !reflect.DeepEqual(cmd.StringSlice("intervals"), []string{"20", "30", "40"}) {
 				t.Errorf("main name not set from env")
 			}
-			if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
+			if !reflect.DeepEqual(cmd.StringSlice("i"), []string{"20", "30", "40"}) {
 				t.Errorf("short name not set from env")
 			}
 			return nil
@@ -1887,11 +1889,11 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{"1", "2", "5"}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
-			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
+		Action: func(_ context.Context, cmd *Command) error {
+			if !reflect.DeepEqual(cmd.StringSlice("intervals"), []string{"20", "30", "40"}) {
 				t.Errorf("main name not set from env")
 			}
-			if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
+			if !reflect.DeepEqual(cmd.StringSlice("i"), []string{"20", "30", "40"}) {
 				t.Errorf("short name not set from env")
 			}
 			return nil
@@ -1909,7 +1911,7 @@ func TestParseMultiStringSliceFromEnvWithDestination(t *testing.T) {
 		Flags: []Flag{
 			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(*Context) error {
+		Action: func(context.Context, *Command) error {
 			if !reflect.DeepEqual(dest, []string{"20", "30", "40"}) {
 				t.Errorf("main name not set from env")
 			}
@@ -1926,11 +1928,11 @@ func TestParseMultiInt(t *testing.T) {
 		Flags: []Flag{
 			&IntFlag{Name: "serve", Aliases: []string{"s"}},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Int("serve") != 10 {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Int("serve") != 10 {
 				t.Errorf("main name not set")
 			}
-			if ctx.Int("s") != 10 {
+			if cmd.Int("s") != 10 {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -1947,7 +1949,7 @@ func TestParseDestinationInt(t *testing.T) {
 				Destination: &dest,
 			},
 		},
-		Action: func(*Context) error {
+		Action: func(context.Context, *Command) error {
 			if dest != 10 {
 				t.Errorf("expected destination Int 10")
 			}
@@ -1964,11 +1966,11 @@ func TestParseMultiIntFromEnv(t *testing.T) {
 		Flags: []Flag{
 			&IntFlag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("APP_TIMEOUT_SECONDS")},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Int("timeout") != 10 {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Int("timeout") != 10 {
 				t.Errorf("main name not set")
 			}
-			if ctx.Int("t") != 10 {
+			if cmd.Int("t") != 10 {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -1977,18 +1979,16 @@ func TestParseMultiIntFromEnv(t *testing.T) {
 }
 
 func TestParseMultiIntFromEnvCascade(t *testing.T) {
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_TIMEOUT_SECONDS", "10")
+	t.Setenv("APP_TIMEOUT_SECONDS", "10")
 	_ = (&Command{
 		Flags: []Flag{
 			&IntFlag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS")},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Int("timeout") != 10 {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Int("timeout") != 10 {
 				t.Errorf("main name not set")
 			}
-			if ctx.Int("t") != 10 {
+			if cmd.Int("t") != 10 {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -2001,11 +2001,11 @@ func TestParseMultiIntSlice(t *testing.T) {
 		Flags: []Flag{
 			&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: []int64{}},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			r := require.New(t)
 
-			r.Equalf([]int64{10, 20}, ctx.IntSlice("serve"), "main name not set")
-			r.Equalf([]int64{10, 20}, ctx.IntSlice("s"), "short name not set")
+			r.Equalf([]int64{10, 20}, cmd.IntSlice("serve"), "main name not set")
+			r.Equalf([]int64{10, 20}, cmd.IntSlice("s"), "short name not set")
 
 			return nil
 		},
@@ -2017,11 +2017,11 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) {
 		Flags: []Flag{
 			&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: []int64{9, 2}},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			r := require.New(t)
 
-			r.Equalf([]int64{10, 20}, ctx.IntSlice("serve"), "main name not set")
-			r.Equalf([]int64{10, 20}, ctx.IntSlice("s"), "short name not set")
+			r.Equalf([]int64{10, 20}, cmd.IntSlice("serve"), "main name not set")
+			r.Equalf([]int64{10, 20}, cmd.IntSlice("s"), "short name not set")
 
 			return nil
 		},
@@ -2033,11 +2033,11 @@ func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) {
 		Flags: []Flag{
 			&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: []int64{9, 2}},
 		},
-		Action: func(ctx *Context) error {
-			if !reflect.DeepEqual(ctx.IntSlice("serve"), []int64{9, 2}) {
+		Action: func(_ context.Context, cmd *Command) error {
+			if !reflect.DeepEqual(cmd.IntSlice("serve"), []int64{9, 2}) {
 				t.Errorf("main name not set")
 			}
-			if !reflect.DeepEqual(ctx.IntSlice("s"), []int64{9, 2}) {
+			if !reflect.DeepEqual(cmd.IntSlice("s"), []int64{9, 2}) {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -2052,11 +2052,11 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) {
 		Flags: []Flag{
 			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int64{}, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			r := require.New(t)
 
-			r.Equalf([]int64{20, 30, 40}, ctx.IntSlice("intervals"), "main name not set from env")
-			r.Equalf([]int64{20, 30, 40}, ctx.IntSlice("i"), "short name not set from env")
+			r.Equalf([]int64{20, 30, 40}, cmd.IntSlice("intervals"), "main name not set from env")
+			r.Equalf([]int64{20, 30, 40}, cmd.IntSlice("i"), "short name not set from env")
 
 			return nil
 		},
@@ -2072,11 +2072,11 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) {
 		Flags: []Flag{
 			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int64{1, 2, 5}, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
-			if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int64{20, 30, 40}) {
+		Action: func(_ context.Context, cmd *Command) error {
+			if !reflect.DeepEqual(cmd.IntSlice("intervals"), []int64{20, 30, 40}) {
 				t.Errorf("main name not set from env")
 			}
-			if !reflect.DeepEqual(ctx.IntSlice("i"), []int64{20, 30, 40}) {
+			if !reflect.DeepEqual(cmd.IntSlice("i"), []int64{20, 30, 40}) {
 				t.Errorf("short name not set from env")
 			}
 			return nil
@@ -2091,11 +2091,11 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
 		Flags: []Flag{
 			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int64{}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
-		Action: func(ctx *Context) error {
+		Action: func(_ context.Context, cmd *Command) error {
 			r := require.New(t)
 
-			r.Equalf([]int64{20, 30, 40}, ctx.IntSlice("intervals"), "main name not set from env")
-			r.Equalf([]int64{20, 30, 40}, ctx.IntSlice("i"), "short name not set from env")
+			r.Equalf([]int64{20, 30, 40}, cmd.IntSlice("intervals"), "main name not set from env")
+			r.Equalf([]int64{20, 30, 40}, cmd.IntSlice("i"), "short name not set from env")
 
 			return nil
 		},
@@ -2107,11 +2107,11 @@ func TestParseMultiFloat64(t *testing.T) {
 		Flags: []Flag{
 			&FloatFlag{Name: "serve", Aliases: []string{"s"}},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Float("serve") != 10.2 {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Float("serve") != 10.2 {
 				t.Errorf("main name not set")
 			}
-			if ctx.Float("s") != 10.2 {
+			if cmd.Float("s") != 10.2 {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -2128,7 +2128,7 @@ func TestParseDestinationFloat64(t *testing.T) {
 				Destination: &dest,
 			},
 		},
-		Action: func(*Context) error {
+		Action: func(context.Context, *Command) error {
 			if dest != 10.2 {
 				t.Errorf("expected destination Float64 10.2")
 			}
@@ -2138,18 +2138,16 @@ func TestParseDestinationFloat64(t *testing.T) {
 }
 
 func TestParseMultiFloat64FromEnv(t *testing.T) {
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
+	t.Setenv("APP_TIMEOUT_SECONDS", "15.5")
 	_ = (&Command{
 		Flags: []Flag{
 			&FloatFlag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("APP_TIMEOUT_SECONDS")},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Float("timeout") != 15.5 {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Float("timeout") != 15.5 {
 				t.Errorf("main name not set")
 			}
-			if ctx.Float("t") != 15.5 {
+			if cmd.Float("t") != 15.5 {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -2158,19 +2156,17 @@ func TestParseMultiFloat64FromEnv(t *testing.T) {
 }
 
 func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
+	t.Setenv("APP_TIMEOUT_SECONDS", "15.5")
 
 	_ = (&Command{
 		Flags: []Flag{
 			&FloatFlag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS")},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Float("timeout") != 15.5 {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Float("timeout") != 15.5 {
 				t.Errorf("main name not set")
 			}
-			if ctx.Float("t") != 15.5 {
+			if cmd.Float("t") != 15.5 {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -2179,32 +2175,28 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
 }
 
 func TestParseMultiFloat64SliceFromEnv(t *testing.T) {
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_INTERVALS", "0.1,-10.5")
+	t.Setenv("APP_INTERVALS", "0.1,-10.5")
 
 	_ = (&Command{
 		Flags: []Flag{
 			&FloatSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []float64{}, Sources: EnvVars("APP_INTERVALS")},
 		},
-		Action: func(cCtx *Context) error {
-			require.Equalf(t, []float64{0.1, -10.5}, cCtx.FloatSlice("intervals"), "main name not set from env")
+		Action: func(_ context.Context, cmd *Command) error {
+			require.Equalf(t, []float64{0.1, -10.5}, cmd.FloatSlice("intervals"), "main name not set from env")
 			return nil
 		},
 	}).Run(buildTestContext(t), []string{"run"})
 }
 
 func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) {
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_INTERVALS", "0.1234,-10.5")
+	t.Setenv("APP_INTERVALS", "0.1234,-10.5")
 
 	_ = (&Command{
 		Flags: []Flag{
 			&FloatSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []float64{}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
-		Action: func(cCtx *Context) error {
-			require.Equalf(t, []float64{0.1234, -10.5}, cCtx.FloatSlice("intervals"), "main name not set from env")
+		Action: func(_ context.Context, cmd *Command) error {
+			require.Equalf(t, []float64{0.1234, -10.5}, cmd.FloatSlice("intervals"), "main name not set from env")
 			return nil
 		},
 	}).Run(buildTestContext(t), []string{"run"})
@@ -2215,11 +2207,11 @@ func TestParseMultiBool(t *testing.T) {
 		Flags: []Flag{
 			&BoolFlag{Name: "serve", Aliases: []string{"s"}},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Bool("serve") != true {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Bool("serve") != true {
 				t.Errorf("main name not set")
 			}
-			if ctx.Bool("s") != true {
+			if cmd.Bool("s") != true {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -2233,11 +2225,11 @@ func TestParseBoolShortOptionHandle(t *testing.T) {
 			{
 				Name:                   "foobar",
 				UseShortOptionHandling: true,
-				Action: func(ctx *Context) error {
-					if ctx.Bool("serve") != true {
+				Action: func(_ context.Context, cmd *Command) error {
+					if cmd.Bool("serve") != true {
 						t.Errorf("main name not set")
 					}
-					if ctx.Bool("option") != true {
+					if cmd.Bool("option") != true {
 						t.Errorf("short name not set")
 					}
 					return nil
@@ -2260,7 +2252,7 @@ func TestParseDestinationBool(t *testing.T) {
 				Destination: &dest,
 			},
 		},
-		Action: func(*Context) error {
+		Action: func(context.Context, *Command) error {
 			if dest != true {
 				t.Errorf("expected destination Bool true")
 			}
@@ -2270,18 +2262,16 @@ func TestParseDestinationBool(t *testing.T) {
 }
 
 func TestParseMultiBoolFromEnv(t *testing.T) {
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_DEBUG", "1")
+	t.Setenv("APP_DEBUG", "1")
 	_ = (&Command{
 		Flags: []Flag{
 			&BoolFlag{Name: "debug", Aliases: []string{"d"}, Sources: EnvVars("APP_DEBUG")},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Bool("debug") != true {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Bool("debug") != true {
 				t.Errorf("main name not set from env")
 			}
-			if ctx.Bool("d") != true {
+			if cmd.Bool("d") != true {
 				t.Errorf("short name not set from env")
 			}
 			return nil
@@ -2290,18 +2280,16 @@ func TestParseMultiBoolFromEnv(t *testing.T) {
 }
 
 func TestParseMultiBoolFromEnvCascade(t *testing.T) {
-	defer resetEnv(os.Environ())
-	os.Clearenv()
-	_ = os.Setenv("APP_DEBUG", "1")
+	t.Setenv("APP_DEBUG", "1")
 	_ = (&Command{
 		Flags: []Flag{
 			&BoolFlag{Name: "debug", Aliases: []string{"d"}, Sources: EnvVars("COMPAT_DEBUG", "APP_DEBUG")},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Bool("debug") != true {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Bool("debug") != true {
 				t.Errorf("main name not set from env")
 			}
-			if ctx.Bool("d") != true {
+			if cmd.Bool("d") != true {
 				t.Errorf("short name not set from env")
 			}
 			return nil
@@ -2321,23 +2309,23 @@ func TestParseBoolFromEnv(t *testing.T) {
 	}
 
 	for _, test := range boolFlagTests {
-		defer resetEnv(os.Environ())
-		os.Clearenv()
-		_ = os.Setenv("DEBUG", test.input)
-		_ = (&Command{
-			Flags: []Flag{
-				&BoolFlag{Name: "debug", Aliases: []string{"d"}, Sources: EnvVars("DEBUG")},
-			},
-			Action: func(ctx *Context) error {
-				if ctx.Bool("debug") != test.output {
-					t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("debug"))
-				}
-				if ctx.Bool("d") != test.output {
-					t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("d"))
-				}
-				return nil
-			},
-		}).Run(buildTestContext(t), []string{"run"})
+		t.Run(fmt.Sprintf("%[1]q %[2]v", test.input, test.output), func(t *testing.T) {
+			t.Setenv("DEBUG", test.input)
+			_ = (&Command{
+				Flags: []Flag{
+					&BoolFlag{Name: "debug", Aliases: []string{"d"}, Sources: EnvVars("DEBUG")},
+				},
+				Action: func(_ context.Context, cmd *Command) error {
+					if cmd.Bool("debug") != test.output {
+						t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, cmd.Bool("debug"))
+					}
+					if cmd.Bool("d") != test.output {
+						t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, cmd.Bool("d"))
+					}
+					return nil
+				},
+			}).Run(buildTestContext(t), []string{"run"})
+		})
 	}
 }
 
@@ -2346,11 +2334,11 @@ func TestParseMultiBoolT(t *testing.T) {
 		Flags: []Flag{
 			&BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Bool("implode") {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Bool("implode") {
 				t.Errorf("main name not set")
 			}
-			if ctx.Bool("i") {
+			if cmd.Bool("i") {
 				t.Errorf("short name not set")
 			}
 			return nil
@@ -2540,13 +2528,13 @@ func TestTimestampFlagApply_Timezoned(t *testing.T) {
 	expect(t, set.Lookup("time").Value.(flag.Getter).Get(), expectedResult.In(pdt))
 }
 
-func TestTimestampFlagValueFromContext(t *testing.T) {
+func TestTimestampFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	now := time.Now()
 	set.Var(newTimestamp(now), "myflag", "doc")
-	ctx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &TimestampFlag{Name: "myflag"}
-	expect(t, f.Get(ctx), now)
+	require.Equal(t, now, f.Get(cmd))
 }
 
 type flagDefaultTestCase struct {
@@ -2844,38 +2832,37 @@ func TestTimestampFlagApply_WithDestination(t *testing.T) {
 func TestSliceShortOptionHandle(t *testing.T) {
 	wasCalled := false
 	err := (&Command{
-		Commands: []*Command{
-			{
-				Name:                   "foobar",
-				UseShortOptionHandling: true,
-				Action: func(ctx *Context) error {
-					wasCalled = true
-					if ctx.Bool("i") != true {
-						t.Error("bool i not set")
-					}
-					if ctx.Bool("t") != true {
-						t.Error("bool i not set")
-					}
-					ss := ctx.StringSlice("net")
-					if !reflect.DeepEqual(ss, []string{"foo"}) {
-						t.Errorf("Got different slice(%v) than expected", ss)
-					}
-					return nil
-				},
-				Flags: []Flag{
-					&StringSliceFlag{Name: "net"},
-					&BoolFlag{Name: "i"},
-					&BoolFlag{Name: "t"},
-				},
-			},
+		Name:                   "foobar",
+		UseShortOptionHandling: true,
+		Action: func(_ context.Context, cmd *Command) error {
+			wasCalled = true
+
+			if !cmd.Bool("i") {
+				return fmt.Errorf("bool i not set")
+			}
+
+			if !cmd.Bool("t") {
+				return fmt.Errorf("bool i not set")
+			}
+
+			ss := cmd.StringSlice("net")
+			if !reflect.DeepEqual(ss, []string{"foo"}) {
+				return fmt.Errorf("got different slice %q than expected", ss)
+			}
+
+			return nil
 		},
-	}).Run(buildTestContext(t), []string{"run", "foobar", "--net=foo", "-it"})
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !wasCalled {
-		t.Fatal("Action callback was never called")
-	}
+		Flags: []Flag{
+			&StringSliceFlag{Name: "net"},
+			&BoolFlag{Name: "i"},
+			&BoolFlag{Name: "t"},
+		},
+	}).Run(buildTestContext(t), []string{"foobar", "--net=foo", "-it"})
+
+	r := require.New(t)
+
+	r.NoError(err)
+	r.Truef(wasCalled, "action callback was never called")
 }
 
 // Test issue #1541
@@ -3003,12 +2990,12 @@ func TestStringMapFlagApply_DefaultValueWithDestination(t *testing.T) {
 	expect(t, defValue, *fl.Destination)
 }
 
-func TestStringMapFlagValueFromContext(t *testing.T) {
+func TestStringMapFlagValueFromCommand(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set.Var(NewStringMap(map[string]string{"a": "b", "c": ""}), "myflag", "doc")
-	ctx := NewContext(nil, set, nil)
+	cmd := &Command{flagSet: set}
 	f := &StringMapFlag{Name: "myflag"}
-	expect(t, f.Get(ctx), map[string]string{"a": "b", "c": ""})
+	require.Equal(t, map[string]string{"a": "b", "c": ""}, f.Get(cmd))
 }
 
 func TestStringMapFlagApply_Error(t *testing.T) {
diff --git a/flag_timestamp.go b/flag_timestamp.go
index fa67513a46..6c5cf963a1 100644
--- a/flag_timestamp.go
+++ b/flag_timestamp.go
@@ -85,18 +85,25 @@ func (t *timestampValue) Get() any {
 }
 
 // Timestamp gets the timestamp from a flag name
-func (cCtx *Context) Timestamp(name string) *time.Time {
-	if fs := cCtx.lookupFlagSet(name); fs != nil {
-		return lookupTimestamp(name, fs)
+func (cmd *Command) Timestamp(name string) *time.Time {
+	if fs := cmd.lookupFlagSet(name); fs != nil {
+		return lookupTimestamp(name, fs, cmd.Name)
 	}
 	return nil
 }
 
 // Fetches the timestamp value from the local timestampWrap
-func lookupTimestamp(name string, set *flag.FlagSet) *time.Time {
-	f := set.Lookup(name)
-	if f != nil {
-		return (f.Value.(*timestampValue)).Value()
+func lookupTimestamp(name string, set *flag.FlagSet, cmdName string) *time.Time {
+	fl := set.Lookup(name)
+	if fl != nil {
+		if tv, ok := fl.Value.(*timestampValue); ok {
+			v := tv.Value()
+
+			tracef("timestamp available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmdName)
+			return v
+		}
 	}
+
+	tracef("timestamp NOT available for flag name %[1]q (cmd=%[2]q)", name, cmdName)
 	return nil
 }
diff --git a/flag_uint.go b/flag_uint.go
index db9957c510..1f3c82da6d 100644
--- a/flag_uint.go
+++ b/flag_uint.go
@@ -43,9 +43,12 @@ func (i *uintValue) String() string { return strconv.FormatUint(uint64(*i.val),
 
 // Uint looks up the value of a local Uint64Flag, returns
 // 0 if not found
-func (cCtx *Context) Uint(name string) uint64 {
-	if v, ok := cCtx.Value(name).(uint64); ok {
+func (cmd *Command) Uint(name string) uint64 {
+	if v, ok := cmd.Value(name).(uint64); ok {
+		tracef("uint available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("uint NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return 0
 }
diff --git a/flag_uint_slice.go b/flag_uint_slice.go
index b25c46f1c6..cce21ef3f7 100644
--- a/flag_uint_slice.go
+++ b/flag_uint_slice.go
@@ -7,9 +7,12 @@ var NewUintSlice = NewSliceBase[uint64, IntegerConfig, uintValue]
 
 // UintSlice looks up the value of a local UintSliceFlag, returns
 // nil if not found
-func (cCtx *Context) UintSlice(name string) []uint64 {
-	if v, ok := cCtx.Value(name).([]uint64); ok {
+func (cmd *Command) UintSlice(name string) []uint64 {
+	if v, ok := cmd.Value(name).([]uint64); ok {
+		tracef("uint slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
 		return v
 	}
+
+	tracef("uint slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
 	return nil
 }
diff --git a/funcs.go b/funcs.go
index 5fba4d8f46..9a7361d81c 100644
--- a/funcs.go
+++ b/funcs.go
@@ -1,35 +1,37 @@
 package cli
 
+import "context"
+
 // ShellCompleteFunc is an action to execute when the shell completion flag is set
-type ShellCompleteFunc func(*Context)
+type ShellCompleteFunc func(context.Context, *Command)
 
 // BeforeFunc is an action that executes prior to any subcommands being run once
 // the context is ready.  If a non-nil error is returned, no subcommands are
 // run.
-type BeforeFunc func(*Context) error
+type BeforeFunc func(context.Context, *Command) error
 
 // AfterFunc is an action that executes after any subcommands are run and have
 // finished. The AfterFunc is run even if Action() panics.
-type AfterFunc func(*Context) error
+type AfterFunc func(context.Context, *Command) error
 
 // ActionFunc is the action to execute when no subcommands are specified
-type ActionFunc func(*Context) error
+type ActionFunc func(context.Context, *Command) error
 
 // CommandNotFoundFunc is executed if the proper command cannot be found
-type CommandNotFoundFunc func(*Context, string)
+type CommandNotFoundFunc func(context.Context, *Command, string)
 
 // OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying
 // customized usage error messages.  This function is able to replace the
 // original error messages.  If this function is not set, the "Incorrect usage"
 // is displayed and the execution is interrupted.
-type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error
+type OnUsageErrorFunc func(ctx context.Context, cmd *Command, err error, isSubcommand bool) error
 
 // InvalidFlagAccessFunc is executed when an invalid flag is accessed from the context.
-type InvalidFlagAccessFunc func(*Context, string)
+type InvalidFlagAccessFunc func(context.Context, *Command, string)
 
 // ExitErrHandlerFunc is executed if provided in order to handle exitError values
 // returned by Actions and Before/After functions.
-type ExitErrHandlerFunc func(cCtx *Context, err error)
+type ExitErrHandlerFunc func(context.Context, *Command, error)
 
 // FlagStringFunc is used by the help generation to display a flag, which is
 // expected to be a single line.
diff --git a/godoc-current.txt b/godoc-current.txt
index 82aa6f4c8c..5229697ea0 100644
--- a/godoc-current.txt
+++ b/godoc-current.txt
@@ -156,11 +156,11 @@ var HelpPrinterCustom helpPrinterCustom = printHelpCustom
 
 FUNCTIONS
 
-func DefaultAppComplete(cCtx *Context)
+func DefaultAppComplete(ctx context.Context, cmd *Command)
     DefaultAppComplete prints the list of subcommands as the default app
     completion method
 
-func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context)
+func DefaultCompleteWithFlags(cmd *Command) func(ctx context.Context, cmd *Command)
 func FlagNames(name string, aliases []string) []string
 func HandleExitCoder(err error)
     HandleExitCoder handles errors implementing ExitCoder by printing their
@@ -172,42 +172,42 @@ func HandleExitCoder(err error)
 
     This function is the default error-handling behavior for an App.
 
-func ShowAppHelp(cCtx *Context) error
+func ShowAppHelp(cmd *Command) error
     ShowAppHelp is an action that displays the help.
 
-func ShowAppHelpAndExit(c *Context, exitCode int)
+func ShowAppHelpAndExit(cmd *Command, exitCode int)
     ShowAppHelpAndExit - Prints the list of subcommands for the app and exits
     with exit code.
 
-func ShowCommandHelp(cCtx *Context, commandName string) error
+func ShowCommandHelp(ctx context.Context, cmd *Command, commandName string) error
     ShowCommandHelp prints help for the given command
 
-func ShowCommandHelpAndExit(c *Context, command string, code int)
+func ShowCommandHelpAndExit(ctx context.Context, cmd *Command, command string, code int)
     ShowCommandHelpAndExit - exits with code after showing help
 
-func ShowSubcommandHelp(cCtx *Context) error
+func ShowSubcommandHelp(cmd *Command) error
     ShowSubcommandHelp prints help for the given subcommand
 
-func ShowSubcommandHelpAndExit(c *Context, exitCode int)
+func ShowSubcommandHelpAndExit(cmd *Command, exitCode int)
     ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits
     with exit code.
 
-func ShowVersion(cCtx *Context)
+func ShowVersion(cmd *Command)
     ShowVersion prints the version number of the App
 
 
 TYPES
 
-type ActionFunc func(*Context) error
+type ActionFunc func(context.Context, *Command) error
     ActionFunc is the action to execute when no subcommands are specified
 
 type ActionableFlag interface {
-	RunAction(*Context) error
+	RunAction(context.Context, *Command) error
 }
     ActionableFlag is an interface that wraps Flag interface and RunAction
     operation.
 
-type AfterFunc func(*Context) error
+type AfterFunc func(context.Context, *Command) error
     AfterFunc is an action that executes after any subcommands are run and have
     finished. The AfterFunc is run even if Action() panics.
 
@@ -227,7 +227,7 @@ type Args interface {
 	Slice() []string
 }
 
-type BeforeFunc func(*Context) error
+type BeforeFunc func(context.Context, *Command) error
     BeforeFunc is an action that executes prior to any subcommands being run
     once the context is ready. If a non-nil error is returned, no subcommands
     are run.
@@ -258,7 +258,7 @@ func (s *BoolWithInverseFlag) IsSet() bool
 
 func (s *BoolWithInverseFlag) Names() []string
 
-func (s *BoolWithInverseFlag) RunAction(ctx *Context) error
+func (s *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error
 
 func (s *BoolWithInverseFlag) String() string
     Example for BoolFlag{Name: "env"} --env (default: false) || --no-env
@@ -379,8 +379,29 @@ type Command struct {
     string slice of arguments such as os.Args. A given Command may contain Flags
     and sub-commands in Commands.
 
+func (cmd *Command) Args() Args
+    Args returns the command line arguments associated with the command.
+
+func (cmd *Command) Bool(name string) bool
+
 func (cmd *Command) Command(name string) *Command
 
+func (cmd *Command) Count(name string) int
+    Count returns the num of occurrences of this flag
+
+func (cmd *Command) Duration(name string) time.Duration
+
+func (cmd *Command) FlagNames() []string
+    FlagNames returns a slice of flag names used by the this command and all of
+    its parent commands.
+
+func (cmd *Command) Float(name string) float64
+    Int looks up the value of a local IntFlag, returns 0 if not found
+
+func (cmd *Command) FloatSlice(name string) []float64
+    FloatSlice looks up the value of a local FloatSliceFlag, returns nil if not
+    found
+
 func (cmd *Command) FullName() string
     FullName returns the full name of the command. For commands with parents
     this ensures that the parent commands are part of the command path.
@@ -388,21 +409,70 @@ func (cmd *Command) FullName() string
 func (cmd *Command) HasName(name string) bool
     HasName returns true if Command.Name matches given name
 
+func (cmd *Command) Int(name string) int64
+    Int64 looks up the value of a local Int64Flag, returns 0 if not found
+
+func (cmd *Command) IntSlice(name string) []int64
+    IntSlice looks up the value of a local IntSliceFlag, returns nil if not
+    found
+
+func (cmd *Command) IsSet(name string) bool
+    IsSet determines if the flag was actually set
+
+func (cmd *Command) Lineage() []*Command
+    Lineage returns *this* command and all of its ancestor commands in order
+    from child to parent
+
+func (cmd *Command) LocalFlagNames() []string
+    LocalFlagNames returns a slice of flag names used in this command.
+
+func (cmd *Command) NArg() int
+    NArg returns the number of the command line arguments.
+
 func (cmd *Command) Names() []string
     Names returns the names including short names and aliases.
 
+func (cmd *Command) NumFlags() int
+    NumFlags returns the number of flags set
+
 func (cmd *Command) Root() *Command
     Root returns the Command at the root of the graph
 
-func (cmd *Command) Run(ctx context.Context, arguments []string) (deferErr error)
+func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error)
     Run is the entry point to the command graph. The positional arguments are
     parsed according to the Flag and Command definitions and the matching Action
     functions are run.
 
+func (cmd *Command) Set(name, value string) error
+    Set sets a context flag to a value.
+
+func (cmd *Command) String(name string) string
+
+func (cmd *Command) StringMap(name string) map[string]string
+    StringMap looks up the value of a local StringMapFlag, returns nil if not
+    found
+
+func (cmd *Command) StringSlice(name string) []string
+    StringSlice looks up the value of a local StringSliceFlag, returns nil if
+    not found
+
+func (cmd *Command) Timestamp(name string) *time.Time
+    Timestamp gets the timestamp from a flag name
+
 func (cmd *Command) ToFishCompletion() (string, error)
     ToFishCompletion creates a fish completion string for the `*App` The
     function errors if either parsing or writing of the string fails.
 
+func (cmd *Command) Uint(name string) uint64
+    Uint looks up the value of a local Uint64Flag, returns 0 if not found
+
+func (cmd *Command) UintSlice(name string) []uint64
+    UintSlice looks up the value of a local UintSliceFlag, returns nil if not
+    found
+
+func (cmd *Command) Value(name string) interface{}
+    Value returns the value of the flag corresponding to `name`
+
 func (cmd *Command) VisibleCategories() []CommandCategory
     VisibleCategories returns a slice of categories and commands that are
     Hidden=false
@@ -433,92 +503,9 @@ type CommandCategory interface {
 }
     CommandCategory is a category containing commands.
 
-type CommandNotFoundFunc func(*Context, string)
+type CommandNotFoundFunc func(context.Context, *Command, string)
     CommandNotFoundFunc is executed if the proper command cannot be found
 
-type Context struct {
-	context.Context
-	Command *Command
-
-	// Has unexported fields.
-}
-    Context is a type that is passed through to each Handler action in a cli
-    application. Context can be used to retrieve context-specific args and
-    parsed command-line options.
-
-func NewContext(cmd *Command, set *flag.FlagSet, parent *Context) *Context
-    NewContext creates a new context. For use in when invoking a Command action.
-
-func (cCtx *Context) Args() Args
-    Args returns the command line arguments associated with the context.
-
-func (cCtx *Context) Bool(name string) bool
-
-func (cCtx *Context) Count(name string) int
-    Count returns the num of occurrences of this flag
-
-func (cCtx *Context) Duration(name string) time.Duration
-
-func (cCtx *Context) FlagNames() []string
-    FlagNames returns a slice of flag names used by the this context and all of
-    its parent contexts.
-
-func (cCtx *Context) Float(name string) float64
-    Int looks up the value of a local IntFlag, returns 0 if not found
-
-func (cCtx *Context) FloatSlice(name string) []float64
-    FloatSlice looks up the value of a local FloatSliceFlag, returns nil if not
-    found
-
-func (cCtx *Context) Int(name string) int64
-    Int64 looks up the value of a local Int64Flag, returns 0 if not found
-
-func (cCtx *Context) IntSlice(name string) []int64
-    IntSlice looks up the value of a local IntSliceFlag, returns nil if not
-    found
-
-func (cCtx *Context) IsSet(name string) bool
-    IsSet determines if the flag was actually set
-
-func (cCtx *Context) Lineage() []*Context
-    Lineage returns *this* context and all of its ancestor contexts in order
-    from child to parent
-
-func (cCtx *Context) LocalFlagNames() []string
-    LocalFlagNames returns a slice of flag names used in this context.
-
-func (cCtx *Context) NArg() int
-    NArg returns the number of the command line arguments.
-
-func (cCtx *Context) NumFlags() int
-    NumFlags returns the number of flags set
-
-func (cCtx *Context) Set(name, value string) error
-    Set sets a context flag to a value.
-
-func (cCtx *Context) String(name string) string
-
-func (cCtx *Context) StringMap(name string) map[string]string
-    StringMap looks up the value of a local StringMapFlag, returns nil if not
-    found
-
-func (cCtx *Context) StringSlice(name string) []string
-    StringSlice looks up the value of a local StringSliceFlag, returns nil if
-    not found
-
-func (cCtx *Context) Timestamp(name string) *time.Time
-    Timestamp gets the timestamp from a flag name
-
-func (cCtx *Context) Uint(name string) uint64
-    Uint looks up the value of a local Uint64Flag, returns 0 if not found
-
-func (cCtx *Context) UintSlice(name string) []uint64
-    UintSlice looks up the value of a local UintSliceFlag, returns nil if not
-    found
-
-func (cCtx *Context) Value(name string) interface{}
-    Value returns the value of the flag corresponding to `name`
-
 type Countable interface {
 	Count() int
 }
@@ -576,7 +563,7 @@ func Exit(message interface{}, exitCode int) ExitCoder
     can be avoided by overriding the ExitErrHandler function on an App or the
     package-global OsExiter function.
 
-type ExitErrHandlerFunc func(cCtx *Context, err error)
+type ExitErrHandlerFunc func(context.Context, *Command, error)
     ExitErrHandlerFunc is executed if provided in order to handle exitError
     values returned by Actions and Before/After functions.
 
@@ -638,7 +625,7 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 
 	TakesFile bool // whether this flag takes a file argument, mainly for shell completion purposes
 
-	Action func(*Context, T) error // Action callback to be called when flag is set
+	Action func(context.Context, *Command, T) error // Action callback to be called when flag is set
 
 	Config C // Additional/Custom configuration associated with this flag type
 
@@ -658,8 +645,8 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error
     Apply populates the flag given the flag set and environment
 
-func (f *FlagBase[T, C, V]) Get(ctx *Context) T
-    Get returns the flag’s value in the given Context.
+func (f *FlagBase[T, C, V]) Get(cmd *Command) T
+    Get returns the flag’s value in the given Command.
 
 func (f *FlagBase[T, C, V]) GetCategory() string
     GetCategory returns the category of the flag
@@ -696,7 +683,7 @@ func (f *FlagBase[T, C, V]) IsVisible() bool
 func (f *FlagBase[T, C, V]) Names() []string
     Names returns the names of the flag
 
-func (f *FlagBase[T, C, V]) RunAction(ctx *Context) error
+func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error
     RunAction executes flag action if set
 
 func (f *FlagBase[T, C, V]) String() string
@@ -771,7 +758,7 @@ type IntegerConfig struct {
 }
     IntegerConfig is the configuration for all integer type flags
 
-type InvalidFlagAccessFunc func(*Context, string)
+type InvalidFlagAccessFunc func(context.Context, *Command, string)
     InvalidFlagAccessFunc is executed when an invalid flag is accessed from the
     context.
 
@@ -822,7 +809,7 @@ type MutuallyExclusiveFlags struct {
 type NoConfig struct{}
     NoConfig is for flags which dont need a custom configuration
 
-type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error
+type OnUsageErrorFunc func(ctx context.Context, cmd *Command, err error, isSubcommand bool) error
     OnUsageErrorFunc is executed if a usage error occurs. This is useful for
     displaying customized usage error messages. This function is able to replace
     the original error messages. If this function is not set, the "Incorrect
@@ -847,7 +834,7 @@ type Serializer interface {
 }
     Serializer is used to circumvent the limitations of flag.FlagSet.Set
 
-type ShellCompleteFunc func(*Context)
+type ShellCompleteFunc func(context.Context, *Command)
     ShellCompleteFunc is an action to execute when the shell completion flag is
     set
 
diff --git a/help.go b/help.go
index e26cb02b00..2ee9f7658c 100644
--- a/help.go
+++ b/help.go
@@ -1,6 +1,7 @@
 package cli
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"os"
@@ -59,8 +60,8 @@ func buildHelpCommand(withAction bool) *Command {
 	return cmd
 }
 
-func helpCommandAction(cCtx *Context) error {
-	args := cCtx.Args()
+func helpCommandAction(ctx context.Context, cmd *Command) error {
+	args := cmd.Args()
 	firstArg := args.First()
 
 	// This action can be triggered by a "default" action of a command
@@ -78,79 +79,79 @@ func helpCommandAction(cCtx *Context) error {
 	//     to
 	// $ app foo
 	// which will then be handled as case 3
-	if cCtx.parent != nil && (cCtx.Command.HasName(helpName) || cCtx.Command.HasName(helpAlias)) {
-		tracef("setting cCtx to cCtx.parentContext")
-		cCtx = cCtx.parent
+	if cmd.parent != nil && (cmd.HasName(helpName) || cmd.HasName(helpAlias)) {
+		tracef("setting cmd to cmd.parent")
+		cmd = cmd.parent
 	}
 
 	// Case 4. $ app help foo
 	// foo is the command for which help needs to be shown
 	if firstArg != "" {
 		tracef("returning ShowCommandHelp with %[1]q", firstArg)
-		return ShowCommandHelp(cCtx, firstArg)
+		return ShowCommandHelp(ctx, cmd, firstArg)
 	}
 
 	// Case 1 & 2
 	// Special case when running help on main app itself as opposed to individual
 	// commands/subcommands
-	if cCtx.parent.Command == nil {
+	if cmd.parent == nil {
 		tracef("returning ShowAppHelp")
-		_ = ShowAppHelp(cCtx)
+		_ = ShowAppHelp(cmd)
 		return nil
 	}
 
 	// Case 3, 5
-	if (len(cCtx.Command.Commands) == 1 && !cCtx.Command.HideHelp) ||
-		(len(cCtx.Command.Commands) == 0 && cCtx.Command.HideHelp) {
+	if (len(cmd.Commands) == 1 && !cmd.HideHelp) ||
+		(len(cmd.Commands) == 0 && cmd.HideHelp) {
 
-		tmpl := cCtx.Command.CustomHelpTemplate
+		tmpl := cmd.CustomHelpTemplate
 		if tmpl == "" {
 			tmpl = CommandHelpTemplate
 		}
 
-		tracef("running HelpPrinter with command %[1]q", cCtx.Command.Name)
-		HelpPrinter(cCtx.Command.Root().Writer, tmpl, cCtx.Command)
+		tracef("running HelpPrinter with command %[1]q", cmd.Name)
+		HelpPrinter(cmd.Root().Writer, tmpl, cmd)
 
 		return nil
 	}
 
 	tracef("running ShowSubcommandHelp")
-	return ShowSubcommandHelp(cCtx)
+	return ShowSubcommandHelp(cmd)
 }
 
 // ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
-func ShowAppHelpAndExit(c *Context, exitCode int) {
-	_ = ShowAppHelp(c)
+func ShowAppHelpAndExit(cmd *Command, exitCode int) {
+	_ = ShowAppHelp(cmd)
 	os.Exit(exitCode)
 }
 
 // ShowAppHelp is an action that displays the help.
-func ShowAppHelp(cCtx *Context) error {
-	tmpl := cCtx.Command.CustomRootCommandHelpTemplate
+func ShowAppHelp(cmd *Command) error {
+	tmpl := cmd.CustomRootCommandHelpTemplate
 	if tmpl == "" {
 		tracef("using RootCommandHelpTemplate")
 		tmpl = RootCommandHelpTemplate
 	}
 
-	if cCtx.Command.ExtraInfo == nil {
-		HelpPrinter(cCtx.Command.Root().Writer, tmpl, cCtx.Command.Root())
+	if cmd.ExtraInfo == nil {
+		HelpPrinter(cmd.Root().Writer, tmpl, cmd.Root())
 		return nil
 	}
 
 	tracef("setting ExtraInfo in customAppData")
 	customAppData := func() map[string]any {
 		return map[string]any{
-			"ExtraInfo": cCtx.Command.ExtraInfo,
+			"ExtraInfo": cmd.ExtraInfo,
 		}
 	}
-	HelpPrinterCustom(cCtx.Command.Root().Writer, tmpl, cCtx.Command.Root(), customAppData())
+	HelpPrinterCustom(cmd.Root().Writer, tmpl, cmd.Root(), customAppData())
 
 	return nil
 }
 
 // DefaultAppComplete prints the list of subcommands as the default app completion method
-func DefaultAppComplete(cCtx *Context) {
-	DefaultCompleteWithFlags(nil)(cCtx)
+func DefaultAppComplete(ctx context.Context, cmd *Command) {
+	DefaultCompleteWithFlags(nil)(ctx, cmd)
 }
 
 func printCommandSuggestions(commands []*Command, writer io.Writer) {
@@ -209,55 +210,55 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
 			// match if last argument matches this flag and it is not repeated
 			if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) {
 				flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
-				_, _ = fmt.Fprintln(writer, flagCompletion)
+				fmt.Fprintln(writer, flagCompletion)
 			}
 		}
 	}
 }
 
-func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) {
-	return func(cCtx *Context) {
+func DefaultCompleteWithFlags(cmd *Command) func(ctx context.Context, cmd *Command) {
+	return func(_ context.Context, cmd *Command) {
 		if len(os.Args) > 2 {
 			lastArg := os.Args[len(os.Args)-2]
 
 			if strings.HasPrefix(lastArg, "-") {
 				if cmd != nil {
-					printFlagSuggestions(lastArg, cmd.Flags, cCtx.Command.Root().Writer)
+					printFlagSuggestions(lastArg, cmd.Flags, cmd.Root().Writer)
 
 					return
 				}
 
-				printFlagSuggestions(lastArg, cCtx.Command.Flags, cCtx.Command.Root().Writer)
+				printFlagSuggestions(lastArg, cmd.Flags, cmd.Root().Writer)
 
 				return
 			}
 		}
 
 		if cmd != nil {
-			printCommandSuggestions(cmd.Commands, cCtx.Command.Root().Writer)
+			printCommandSuggestions(cmd.Commands, cmd.Root().Writer)
 			return
 		}
 
-		printCommandSuggestions(cCtx.Command.Commands, cCtx.Command.Root().Writer)
+		printCommandSuggestions(cmd.Commands, cmd.Root().Writer)
 	}
 }
 
 // ShowCommandHelpAndExit - exits with code after showing help
-func ShowCommandHelpAndExit(c *Context, command string, code int) {
-	_ = ShowCommandHelp(c, command)
+func ShowCommandHelpAndExit(ctx context.Context, cmd *Command, command string, code int) {
+	_ = ShowCommandHelp(ctx, cmd, command)
 	os.Exit(code)
 }
 
 // ShowCommandHelp prints help for the given command
-func ShowCommandHelp(cCtx *Context, commandName string) error {
-	for _, cmd := range cCtx.Command.Commands {
-		if !cmd.HasName(commandName) {
+func ShowCommandHelp(ctx context.Context, cmd *Command, commandName string) error {
+	for _, subCmd := range cmd.Commands {
+		if !subCmd.HasName(commandName) {
 			continue
 		}
 
-		tmpl := cmd.CustomHelpTemplate
+		tmpl := subCmd.CustomHelpTemplate
 		if tmpl == "" {
-			if len(cmd.Commands) == 0 {
+			if len(subCmd.Commands) == 0 {
 				tracef("using CommandHelpTemplate")
 				tmpl = CommandHelpTemplate
 			} else {
@@ -267,7 +268,7 @@ func ShowCommandHelp(cCtx *Context, commandName string) error {
 		}
 
 		tracef("running HelpPrinter")
-		HelpPrinter(cCtx.Command.Root().Writer, tmpl, cmd)
+		HelpPrinter(cmd.Root().Writer, tmpl, subCmd)
 
 		tracef("returning nil after printing help")
 		return nil
@@ -275,11 +276,11 @@ func ShowCommandHelp(cCtx *Context, commandName string) error {
 
 	tracef("no matching command found")
 
-	if cCtx.Command.CommandNotFound == nil {
+	if cmd.CommandNotFound == nil {
 		errMsg := fmt.Sprintf("No help topic for '%v'", commandName)
 
-		if cCtx.Command.Suggest {
-			if suggestion := SuggestCommand(cCtx.Command.Commands, commandName); suggestion != "" {
+		if cmd.Suggest {
+			if suggestion := SuggestCommand(cmd.Commands, commandName); suggestion != "" {
 				errMsg += ". " + suggestion
 			}
 		}
@@ -289,34 +290,35 @@ func ShowCommandHelp(cCtx *Context, commandName string) error {
 	}
 
 	tracef("running CommandNotFound func for %[1]q", commandName)
-	cCtx.Command.CommandNotFound(cCtx, commandName)
+	cmd.CommandNotFound(ctx, cmd, commandName)
 
 	return nil
 }
 
 // ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits with exit code.
-func ShowSubcommandHelpAndExit(c *Context, exitCode int) {
-	_ = ShowSubcommandHelp(c)
+func ShowSubcommandHelpAndExit(cmd *Command, exitCode int) {
+	_ = ShowSubcommandHelp(cmd)
 	os.Exit(exitCode)
 }
 
 // ShowSubcommandHelp prints help for the given subcommand
-func ShowSubcommandHelp(cCtx *Context) error {
-	if cCtx == nil {
+func ShowSubcommandHelp(cmd *Command) error {
+	if cmd == nil {
 		return nil
 	}
 
-	HelpPrinter(cCtx.Command.Root().Writer, SubcommandHelpTemplate, cCtx.Command)
+	HelpPrinter(cmd.Root().Writer, SubcommandHelpTemplate, cmd)
 	return nil
 }
 
 // ShowVersion prints the version number of the App
-func ShowVersion(cCtx *Context) {
-	VersionPrinter(cCtx)
+func ShowVersion(cmd *Command) {
+	tracef("showing version via VersionPrinter (cmd=%[1]q)", cmd.Name)
+	VersionPrinter(cmd)
 }
 
-func printVersion(cCtx *Context) {
-	_, _ = fmt.Fprintf(cCtx.Command.Root().Writer, "%v version %v\n", cCtx.Command.Name, cCtx.Command.Version)
+func printVersion(cmd *Command) {
+	_, _ = fmt.Fprintf(cmd.Root().Writer, "%v version %v\n", cmd.Name, cmd.Version)
 }
 
 func handleTemplateError(err error) {
@@ -418,28 +420,16 @@ func printHelp(out io.Writer, templ string, data interface{}) {
 	HelpPrinterCustom(out, templ, data, nil)
 }
 
-func checkVersion(cCtx *Context) bool {
+func checkVersion(cmd *Command) bool {
 	found := false
 	for _, name := range VersionFlag.Names() {
-		if cCtx.Bool(name) {
+		if cmd.Bool(name) {
 			found = true
 		}
 	}
 	return found
 }
 
-func checkHelp(cCtx *Context) bool {
-	found := false
-	for _, name := range HelpFlag.Names() {
-		if cCtx.Bool(name) {
-			found = true
-			break
-		}
-	}
-
-	return found
-}
-
 func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) {
 	if !c.EnableShellCompletion {
 		return false, arguments
@@ -455,21 +445,23 @@ func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) {
 	return true, arguments[:pos]
 }
 
-func checkCompletions(cCtx *Context) bool {
-	if !cCtx.shellComplete {
+func checkCompletions(ctx context.Context, cmd *Command) bool {
+	tracef("checking completions on command %[1]q", cmd.Name)
+
+	if !cmd.EnableShellCompletion {
 		return false
 	}
 
-	if args := cCtx.Args(); args.Present() {
-		name := args.First()
-		if cmd := cCtx.Command.Command(name); cmd != nil {
+	if argsArguments := cmd.Args(); argsArguments.Present() {
+		name := argsArguments.First()
+		if cmd := cmd.Command(name); cmd != nil {
 			// let the command handle the completion
 			return false
 		}
 	}
 
-	if cCtx.Command != nil && cCtx.Command.ShellComplete != nil {
-		cCtx.Command.ShellComplete(cCtx)
+	if cmd.ShellComplete != nil {
+		cmd.ShellComplete(ctx, cmd)
 	}
 
 	return true
diff --git a/help_test.go b/help_test.go
index 915bd63bd0..d3bee346e0 100644
--- a/help_test.go
+++ b/help_test.go
@@ -2,6 +2,7 @@ package cli
 
 import (
 	"bytes"
+	"context"
 	"flag"
 	"fmt"
 	"io"
@@ -16,10 +17,7 @@ import (
 func Test_ShowAppHelp_NoAuthor(t *testing.T) {
 	output := new(bytes.Buffer)
 	cmd := &Command{Writer: output}
-
-	c := NewContext(cmd, nil, nil)
-
-	_ = ShowAppHelp(c)
+	_ = ShowAppHelp(cmd)
 
 	if bytes.Contains(output.Bytes(), []byte("AUTHOR(S):")) {
 		t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):")
@@ -32,9 +30,7 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) {
 
 	cmd.Version = ""
 
-	c := NewContext(cmd, nil, nil)
-
-	_ = ShowAppHelp(c)
+	_ = ShowAppHelp(cmd)
 
 	if bytes.Contains(output.Bytes(), []byte("VERSION:")) {
 		t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
@@ -47,9 +43,7 @@ func Test_ShowAppHelp_HideVersion(t *testing.T) {
 
 	cmd.HideVersion = true
 
-	c := NewContext(cmd, nil, nil)
-
-	_ = ShowAppHelp(c)
+	_ = ShowAppHelp(cmd)
 
 	if bytes.Contains(output.Bytes(), []byte("VERSION:")) {
 		t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
@@ -63,9 +57,7 @@ func Test_ShowAppHelp_MultiLineDescription(t *testing.T) {
 	cmd.HideVersion = true
 	cmd.Description = "multi\n  line"
 
-	c := NewContext(cmd, nil, nil)
-
-	_ = ShowAppHelp(c)
+	_ = ShowAppHelp(cmd)
 
 	if !bytes.Contains(output.Bytes(), []byte("DESCRIPTION:\n   multi\n     line")) {
 		t.Errorf("expected\n%s\nto include\n%s", output.String(), "DESCRIPTION:\n   multi\n     line")
@@ -90,8 +82,8 @@ func Test_Help_Custom_Flags(t *testing.T) {
 		Flags: []Flag{
 			&BoolFlag{Name: "foo", Aliases: []string{"h"}},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Bool("h") != true {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Bool("h") != true {
 				t.Errorf("custom help flag not set")
 			}
 			return nil
@@ -120,8 +112,8 @@ func Test_Version_Custom_Flags(t *testing.T) {
 		Flags: []Flag{
 			&BoolFlag{Name: "foo", Aliases: []string{"v"}},
 		},
-		Action: func(ctx *Context) error {
-			if ctx.Bool("v") != true {
+		Action: func(_ context.Context, cmd *Command) error {
+			if cmd.Bool("v") != true {
 				t.Errorf("custom version flag not set")
 			}
 			return nil
@@ -134,14 +126,13 @@ func Test_Version_Custom_Flags(t *testing.T) {
 }
 
 func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) {
-	cmd := &Command{}
-
-	set := flag.NewFlagSet("test", 0)
-	_ = set.Parse([]string{"foo"})
+	cmd := &Command{
+		flagSet: flag.NewFlagSet("test", 0),
+	}
 
-	c := NewContext(cmd, set, nil)
+	_ = cmd.flagSet.Parse([]string{"foo"})
 
-	err := helpCommandAction(c)
+	err := helpCommandAction(context.Background(), cmd)
 
 	if err == nil {
 		t.Fatalf("expected error from helpCommandAction(), but got nil")
@@ -220,9 +211,9 @@ func TestHelpCommand_FullName(t *testing.T) {
 		},
 	}
 	for _, tc := range testCases {
-		out := &bytes.Buffer{}
-
 		t.Run(tc.name, func(t *testing.T) {
+			out := &bytes.Buffer{}
+
 			if tc.skip {
 				t.SkipNow()
 			}
@@ -275,14 +266,12 @@ func Test_helpCommand_HideHelpFlag(t *testing.T) {
 }
 
 func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
-	cmd := &Command{}
-
-	set := flag.NewFlagSet("test", 0)
-	_ = set.Parse([]string{"foo"})
-
-	c := NewContext(cmd, set, nil)
+	cmd := &Command{
+		flagSet: flag.NewFlagSet("test", 0),
+	}
+	_ = cmd.flagSet.Parse([]string{"foo"})
 
-	err := helpCommandAction(c)
+	err := helpCommandAction(context.Background(), cmd)
 
 	if err == nil {
 		t.Fatalf("expected error from helpCommandAction(), but got nil")
@@ -310,7 +299,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) {
 			{
 				Name:    "frobbly",
 				Aliases: []string{"fr", "frob"},
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
@@ -568,7 +557,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) {
 			{
 				Name:    "frobbly",
 				Aliases: []string{"fr", "frob", "bork"},
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
@@ -586,7 +575,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
 			{
 				Name:    "frobbly",
 				Aliases: []string{"fr", "frob", "bork"},
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
@@ -609,7 +598,7 @@ func TestShowCommandHelp_Customtemplate(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "frobbly",
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 				CustomHelpTemplate: `NAME:
@@ -761,14 +750,14 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "frobbly",
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
 			{
 				Name:   "secretfrob",
 				Hidden: true,
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
@@ -932,14 +921,14 @@ func TestShowAppHelp_CustomAppTemplate(t *testing.T) {
 		Commands: []*Command{
 			{
 				Name: "frobbly",
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
 			{
 				Name:   "secretfrob",
 				Hidden: true,
-				Action: func(ctx *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
@@ -1172,60 +1161,46 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) {
 }
 
 func TestDefaultCompleteWithFlags(t *testing.T) {
-	origEnv := os.Environ()
 	origArgv := os.Args
+	t.Cleanup(func() { os.Args = origArgv })
 
-	t.Cleanup(func() {
-		os.Args = origArgv
-		resetEnv(origEnv)
-	})
-
-	os.Setenv("SHELL", "bash")
+	t.Setenv("SHELL", "bash")
 
 	for _, tc := range []struct {
 		name     string
-		c        *Context
 		cmd      *Command
 		argv     []string
 		expected string
 	}{
 		{
 			name:     "empty",
-			c:        &Context{Command: &Command{}},
 			cmd:      &Command{},
 			argv:     []string{"prog", "cmd"},
 			expected: "",
 		},
 		{
 			name: "typical-flag-suggestion",
-			c: &Context{Command: &Command{
-				Name: "cmd",
-				Flags: []Flag{
-					&BoolFlag{Name: "happiness"},
-					&IntFlag{Name: "everybody-jump-on"},
-				},
-				Commands: []*Command{
-					{Name: "putz"},
-				},
-			}},
 			cmd: &Command{
 				Flags: []Flag{
 					&BoolFlag{Name: "excitement"},
 					&StringFlag{Name: "hat-shape"},
 				},
+				parent: &Command{
+					Name: "cmd",
+					Flags: []Flag{
+						&BoolFlag{Name: "happiness"},
+						&IntFlag{Name: "everybody-jump-on"},
+					},
+					Commands: []*Command{
+						{Name: "putz"},
+					},
+				},
 			},
 			argv:     []string{"cmd", "--e", "--generate-shell-completion"},
 			expected: "--excitement\n",
 		},
 		{
 			name: "typical-command-suggestion",
-			c: &Context{Command: &Command{
-				Name: "cmd",
-				Flags: []Flag{
-					&BoolFlag{Name: "happiness"},
-					&IntFlag{Name: "everybody-jump-on"},
-				},
-			}},
 			cmd: &Command{
 				Name: "putz",
 				Commands: []*Command{
@@ -1235,19 +1210,19 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
 					&BoolFlag{Name: "excitement"},
 					&StringFlag{Name: "hat-shape"},
 				},
+				parent: &Command{
+					Name: "cmd",
+					Flags: []Flag{
+						&BoolFlag{Name: "happiness"},
+						&IntFlag{Name: "everybody-jump-on"},
+					},
+				},
 			},
 			argv:     []string{"cmd", "--generate-shell-completion"},
 			expected: "futz\n",
 		},
 		{
 			name: "autocomplete-with-spaces",
-			c: &Context{Command: &Command{
-				Name: "cmd",
-				Flags: []Flag{
-					&BoolFlag{Name: "happiness"},
-					&IntFlag{Name: "everybody-jump-on"},
-				},
-			}},
 			cmd: &Command{
 				Name: "putz",
 				Commands: []*Command{
@@ -1257,6 +1232,13 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
 					&BoolFlag{Name: "excitement"},
 					&StringFlag{Name: "hat-shape"},
 				},
+				parent: &Command{
+					Name: "cmd",
+					Flags: []Flag{
+						&BoolFlag{Name: "happiness"},
+						&IntFlag{Name: "everybody-jump-on"},
+					},
+				},
 			},
 			argv:     []string{"cmd", "--url", "http://localhost:8000", "h", "--generate-shell-completion"},
 			expected: "help\n",
@@ -1264,11 +1246,12 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
 	} {
 		t.Run(tc.name, func(ct *testing.T) {
 			writer := &bytes.Buffer{}
-			tc.c.Command.Writer = writer
+			rootCmd := tc.cmd.Root()
+			rootCmd.Writer = writer
 
 			os.Args = tc.argv
 			f := DefaultCompleteWithFlags(tc.cmd)
-			f(tc.c)
+			f(context.Background(), tc.cmd)
 
 			written := writer.String()
 
@@ -1317,8 +1300,6 @@ Including newlines.
 And then another long line. Blah blah blah does anybody ever read these things?`,
 	}
 
-	c := NewContext(cmd, nil, nil)
-
 	HelpPrinter = func(w io.Writer, templ string, data interface{}) {
 		funcMap := map[string]interface{}{
 			"wrapAt": func() int {
@@ -1329,7 +1310,7 @@ And then another long line. Blah blah blah does anybody ever read these things?`
 		HelpPrinterCustom(w, templ, data, funcMap)
 	}
 
-	_ = ShowAppHelp(c)
+	_ = ShowAppHelp(cmd)
 
 	expected := `NAME:
     - here's a sample
@@ -1397,16 +1378,14 @@ func TestWrappedCommandHelp(t *testing.T) {
 				Usage:       "add a task to the list",
 				UsageText:   "this is an even longer way of describing adding a task to the list",
 				Description: "and a description long enough to wrap in this test case",
-				Action: func(c *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 			},
 		},
 	}
 	cmd.setupDefaults([]string{"cli.test"})
-
-	cCtx := NewContext(cmd, nil, nil)
-	cmd.setupCommandGraph(cCtx)
+	cmd.setupCommandGraph()
 
 	HelpPrinter = func(w io.Writer, templ string, data interface{}) {
 		funcMap := map[string]interface{}{
@@ -1420,7 +1399,7 @@ func TestWrappedCommandHelp(t *testing.T) {
 
 	r := require.New(t)
 
-	r.NoError(ShowCommandHelp(cCtx, "add"))
+	r.NoError(ShowCommandHelp(context.Background(), cmd, "add"))
 	r.Equal(`NAME:
    cli.test add - add a task
                   to the list
@@ -1465,7 +1444,7 @@ func TestWrappedSubcommandHelp(t *testing.T) {
 				Usage:       "add a task to the list",
 				UsageText:   "this is an even longer way of describing adding a task to the list",
 				Description: "and a description long enough to wrap in this test case",
-				Action: func(c *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 				Commands: []*Command{
@@ -1473,7 +1452,7 @@ func TestWrappedSubcommandHelp(t *testing.T) {
 						Name:      "grok",
 						Usage:     "remove an existing template",
 						UsageText: "longer usage text goes here, la la la, hopefully this is long enough to wrap even more",
-						Action: func(c *Context) error {
+						Action: func(context.Context, *Command) error {
 							return nil
 						},
 					},
@@ -1535,7 +1514,7 @@ func TestWrappedHelpSubcommand(t *testing.T) {
 				Usage:       "add a task to the list",
 				UsageText:   "this is an even longer way of describing adding a task to the list",
 				Description: "and a description long enough to wrap in this test case",
-				Action: func(c *Context) error {
+				Action: func(context.Context, *Command) error {
 					return nil
 				},
 				Commands: []*Command{
@@ -1543,7 +1522,7 @@ func TestWrappedHelpSubcommand(t *testing.T) {
 						Name:      "grok",
 						Usage:     "remove an existing template",
 						UsageText: "longer usage text goes here, la la la, hopefully this is long enough to wrap even more",
-						Action: func(c *Context) error {
+						Action: func(context.Context, *Command) error {
 							return nil
 						},
 						Flags: []Flag{
diff --git a/internal/build/build.go b/internal/build/build.go
index 60017ca7e8..48b809be00 100644
--- a/internal/build/build.go
+++ b/internal/build/build.go
@@ -18,6 +18,7 @@ import (
 	"path/filepath"
 	"runtime"
 	"strings"
+	"time"
 
 	"github.com/urfave/cli/v3"
 )
@@ -42,7 +43,10 @@ const (
 
 func main() {
 	top, err := func() (string, error) {
-		if v, err := sh("git", "rev-parse", "--show-toplevel"); err == nil {
+		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+		defer cancel()
+
+		if v, err := sh(ctx, "git", "rev-parse", "--show-toplevel"); err == nil {
 			return strings.TrimSpace(v), nil
 		}
 
@@ -159,8 +163,8 @@ func main() {
 	}
 }
 
-func sh(exe string, args ...string) (string, error) {
-	cmd := exec.Command(exe, args...)
+func sh(ctx context.Context, exe string, args ...string) (string, error) {
+	cmd := exec.CommandContext(ctx, exe, args...)
 	cmd.Stdin = os.Stdin
 	cmd.Stderr = os.Stderr
 
@@ -170,17 +174,17 @@ func sh(exe string, args ...string) (string, error) {
 }
 
 func topRunAction(arg string, args ...string) cli.ActionFunc {
-	return func(cCtx *cli.Context) error {
-		if err := os.Chdir(cCtx.String("top")); err != nil {
+	return func(ctx context.Context, cmd *cli.Command) error {
+		if err := os.Chdir(cmd.String("top")); err != nil {
 			return err
 		}
 
-		return runCmd(arg, args...)
+		return runCmd(ctx, arg, args...)
 	}
 }
 
-func runCmd(arg string, args ...string) error {
-	cmd := exec.Command(arg, args...)
+func runCmd(ctx context.Context, arg string, args ...string) error {
+	cmd := exec.CommandContext(ctx, arg, args...)
 
 	cmd.Stdin = os.Stdin
 	cmd.Stdout = os.Stdout
@@ -227,14 +231,14 @@ func downloadFile(src, dest string, dirPerm, perm os.FileMode) error {
 	return os.Chmod(dest, perm)
 }
 
-func VetActionFunc(cCtx *cli.Context) error {
-	return runCmd("go", "vet", cCtx.String("top")+"/...")
+func VetActionFunc(ctx context.Context, cmd *cli.Command) error {
+	return runCmd(ctx, "go", "vet", cmd.String("top")+"/...")
 }
 
-func TestActionFunc(c *cli.Context) error {
-	tags := c.String("tags")
+func TestActionFunc(ctx context.Context, cmd *cli.Command) error {
+	tags := cmd.String("tags")
 
-	for _, pkg := range c.StringSlice("packages") {
+	for _, pkg := range cmd.StringSlice("packages") {
 		packageName := "github.com/urfave/cli/v3"
 
 		if pkg != "cli" {
@@ -255,12 +259,12 @@ func TestActionFunc(c *cli.Context) error {
 			packageName,
 		}...)
 
-		if err := runCmd("go", args...); err != nil {
+		if err := runCmd(ctx, "go", args...); err != nil {
 			return err
 		}
 	}
 
-	return testCleanup(c.StringSlice("packages"))
+	return testCleanup(cmd.StringSlice("packages"))
 }
 
 func testCleanup(packages []string) error {
@@ -288,8 +292,8 @@ func testCleanup(packages []string) error {
 	return os.WriteFile("coverage.txt", out.Bytes(), 0644)
 }
 
-func GfmrunActionFunc(cCtx *cli.Context) error {
-	top := cCtx.String("top")
+func GfmrunActionFunc(ctx context.Context, cmd *cli.Command) error {
+	top := cmd.String("top")
 
 	bash, err := exec.LookPath("bash")
 	if err != nil {
@@ -312,9 +316,9 @@ func GfmrunActionFunc(cCtx *cli.Context) error {
 		return err
 	}
 
-	fmt.Fprintf(cCtx.Command.ErrWriter, "# ---> workspace/TMPDIR is %q\n", tmpDir)
+	fmt.Fprintf(cmd.ErrWriter, "# ---> workspace/TMPDIR is %q\n", tmpDir)
 
-	if err := runCmd("go", "work", "init", top); err != nil {
+	if err := runCmd(ctx, "go", "work", "init", top); err != nil {
 		return err
 	}
 
@@ -324,12 +328,12 @@ func GfmrunActionFunc(cCtx *cli.Context) error {
 		return err
 	}
 
-	dirPath := cCtx.Args().Get(0)
+	dirPath := cmd.Args().Get(0)
 	if dirPath == "" {
 		dirPath = "README.md"
 	}
 
-	walk := cCtx.Bool("walk")
+	walk := cmd.Bool("walk")
 	sources := []string{}
 
 	if walk {
@@ -392,7 +396,7 @@ func GfmrunActionFunc(cCtx *cli.Context) error {
 		gfmArgs = append(gfmArgs, "--sources", src)
 	}
 
-	if err := runCmd("gfmrun", gfmArgs...); err != nil {
+	if err := runCmd(ctx, "gfmrun", gfmArgs...); err != nil {
 		return err
 	}
 
@@ -402,7 +406,7 @@ func GfmrunActionFunc(cCtx *cli.Context) error {
 // checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down
 // this was originally inspired by https://github.com/urfave/cli/issues/1055, and followed up on as a part
 // of https://github.com/urfave/cli/issues/1057
-func checkBinarySizeActionFunc(c *cli.Context) (err error) {
+func checkBinarySizeActionFunc(ctx context.Context, cmd *cli.Command) (err error) {
 	const (
 		cliSourceFilePath    = "./internal/example-cli/example-cli.go"
 		cliBuiltFilePath     = "./internal/example-cli/built-example"
@@ -413,16 +417,16 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
 		mbStringFormatter    = "%.1fMB"
 	)
 
-	tags := c.String("tags")
+	tags := cmd.String("tags")
 
 	// get cli example size
-	cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath, tags)
+	cliSize, err := getSize(ctx, cliSourceFilePath, cliBuiltFilePath, tags)
 	if err != nil {
 		return err
 	}
 
 	// get hello world size
-	helloSize, err := getSize(helloSourceFilePath, helloBuiltFilePath, tags)
+	helloSize, err := getSize(ctx, helloSourceFilePath, helloBuiltFilePath, tags)
 	if err != nil {
 		return err
 	}
@@ -484,10 +488,10 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
 	return nil
 }
 
-func GenerateActionFunc(cCtx *cli.Context) error {
-	top := cCtx.String("top")
+func GenerateActionFunc(ctx context.Context, cmd *cli.Command) error {
+	top := cmd.String("top")
 
-	cliDocs, err := sh("go", "doc", "-all", top)
+	cliDocs, err := sh(ctx, "go", "doc", "-all", top)
 	if err != nil {
 		return err
 	}
@@ -499,25 +503,26 @@ func GenerateActionFunc(cCtx *cli.Context) error {
 	)
 }
 
-func DiffCheckActionFunc(cCtx *cli.Context) error {
-	if err := os.Chdir(cCtx.String("top")); err != nil {
+func DiffCheckActionFunc(ctx context.Context, cmd *cli.Command) error {
+	if err := os.Chdir(cmd.String("top")); err != nil {
 		return err
 	}
 
-	if err := runCmd("git", "diff", "--exit-code"); err != nil {
+	if err := runCmd(ctx, "git", "diff", "--exit-code"); err != nil {
 		return err
 	}
 
-	return runCmd("git", "diff", "--cached", "--exit-code")
+	return runCmd(ctx, "git", "diff", "--cached", "--exit-code")
 }
 
-func EnsureGoimportsActionFunc(cCtx *cli.Context) error {
-	top := cCtx.String("top")
+func EnsureGoimportsActionFunc(ctx context.Context, cmd *cli.Command) error {
+	top := cmd.String("top")
 	if err := os.Chdir(top); err != nil {
 		return err
 	}
 
 	if err := runCmd(
+		ctx,
 		"goimports",
 		"-d",
 		filepath.Join(top, "internal/build/build.go"),
@@ -527,18 +532,18 @@ func EnsureGoimportsActionFunc(cCtx *cli.Context) error {
 
 	os.Setenv("GOBIN", filepath.Join(top, ".local/bin"))
 
-	return runCmd("go", "install", "golang.org/x/tools/cmd/goimports@latest")
+	return runCmd(ctx, "go", "install", "golang.org/x/tools/cmd/goimports@latest")
 }
 
-func EnsureGfmrunActionFunc(cCtx *cli.Context) error {
-	top := cCtx.String("top")
+func EnsureGfmrunActionFunc(ctx context.Context, cmd *cli.Command) error {
+	top := cmd.String("top")
 	gfmrunExe := filepath.Join(top, ".local/bin/gfmrun")
 
 	if err := os.Chdir(top); err != nil {
 		return err
 	}
 
-	if v, err := sh(gfmrunExe, "--version"); err == nil && strings.TrimSpace(v) == gfmrunVersion {
+	if v, err := sh(ctx, gfmrunExe, "--version"); err == nil && strings.TrimSpace(v) == gfmrunVersion {
 		return nil
 	}
 
@@ -555,58 +560,59 @@ func EnsureGfmrunActionFunc(cCtx *cli.Context) error {
 	return downloadFile(gfmrunURL.String(), gfmrunExe, 0755, 0755)
 }
 
-func EnsureMkdocsActionFunc(cCtx *cli.Context) error {
-	if err := os.Chdir(cCtx.String("top")); err != nil {
+func EnsureMkdocsActionFunc(ctx context.Context, cmd *cli.Command) error {
+	if err := os.Chdir(cmd.String("top")); err != nil {
 		return err
 	}
 
-	if err := runCmd("mkdocs", "--version"); err == nil {
+	if err := runCmd(ctx, "mkdocs", "--version"); err == nil {
 		return nil
 	}
 
-	if cCtx.Bool("upgrade-pip") {
-		if err := runCmd("pip", "install", "-U", "pip"); err != nil {
+	if cmd.Bool("upgrade-pip") {
+		if err := runCmd(ctx, "pip", "install", "-U", "pip"); err != nil {
 			return err
 		}
 	}
 
-	return runCmd("pip", "install", "-r", "mkdocs-reqs.txt")
+	return runCmd(ctx, "pip", "install", "-r", "mkdocs-reqs.txt")
 }
 
-func SetMkdocsRemoteActionFunc(cCtx *cli.Context) error {
-	ghToken := strings.TrimSpace(cCtx.String("github-token"))
+func SetMkdocsRemoteActionFunc(ctx context.Context, cmd *cli.Command) error {
+	ghToken := strings.TrimSpace(cmd.String("github-token"))
 	if ghToken == "" {
 		return errors.New("empty github token")
 	}
 
-	if err := os.Chdir(cCtx.String("top")); err != nil {
+	if err := os.Chdir(cmd.String("top")); err != nil {
 		return err
 	}
 
-	if err := runCmd("git", "remote", "rm", "origin"); err != nil {
+	if err := runCmd(ctx, "git", "remote", "rm", "origin"); err != nil {
 		return err
 	}
 
 	return runCmd(
+		ctx,
 		"git", "remote", "add", "origin",
 		fmt.Sprintf("https://x-access-token:%[1]s@github.com/urfave/cli.git", ghToken),
 	)
 }
 
-func LintActionFunc(cCtx *cli.Context) error {
-	top := cCtx.String("top")
+func LintActionFunc(ctx context.Context, cmd *cli.Command) error {
+	top := cmd.String("top")
 	if err := os.Chdir(top); err != nil {
 		return err
 	}
 
-	out, err := sh(filepath.Join(top, ".local/bin/goimports"), "-l", ".")
+	out, err := sh(ctx, filepath.Join(top, ".local/bin/goimports"), "-l", ".")
 	if err != nil {
 		return err
 	}
 
 	if strings.TrimSpace(out) != "" {
-		fmt.Fprintln(cCtx.Command.ErrWriter, "# ---> goimports -l is non-empty:")
-		fmt.Fprintln(cCtx.Command.ErrWriter, out)
+		fmt.Fprintln(cmd.ErrWriter, "# ---> goimports -l is non-empty:")
+		fmt.Fprintln(cmd.ErrWriter, out)
 
 		return errors.New("goimports needed")
 	}
@@ -614,17 +620,18 @@ func LintActionFunc(cCtx *cli.Context) error {
 	return nil
 }
 
-func V3Diff(cCtx *cli.Context) error {
-	if err := os.Chdir(cCtx.String("top")); err != nil {
+func V3Diff(ctx context.Context, cmd *cli.Command) error {
+	if err := os.Chdir(cmd.String("top")); err != nil {
 		return err
 	}
 
 	err := runCmd(
+		ctx,
 		"diff",
 		"--ignore-all-space",
 		"--minimal",
 		"--color="+func() string {
-			if cCtx.Bool("color") {
+			if cmd.Bool("color") {
 				return "always"
 			}
 			return "auto"
@@ -644,7 +651,7 @@ func V3Diff(cCtx *cli.Context) error {
 	return err
 }
 
-func getSize(sourcePath, builtPath, tags string) (int64, error) {
+func getSize(ctx context.Context, sourcePath, builtPath, tags string) (int64, error) {
 	args := []string{"build"}
 
 	if tags != "" {
@@ -657,7 +664,7 @@ func getSize(sourcePath, builtPath, tags string) (int64, error) {
 		sourcePath,
 	}...)
 
-	if err := runCmd("go", args...); err != nil {
+	if err := runCmd(ctx, "go", args...); err != nil {
 		fmt.Println("issue getting size for example binary")
 		return 0, err
 	}
diff --git a/parse.go b/parse.go
index d79f15a18e..8056343900 100644
--- a/parse.go
+++ b/parse.go
@@ -18,34 +18,50 @@ type iterativeParser interface {
 // completion when, the user-supplied options may be incomplete.
 func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
 	for {
+		tracef("parsing args %[1]q with %[2]T (name=%[3]q)", args, set, set.Name())
+
 		err := set.Parse(args)
 		if !ip.useShortOptionHandling() || err == nil {
 			if shellComplete {
+				tracef("returning nil due to shellComplete=true")
+
 				return nil
 			}
+
+			tracef("returning err %[1]q", err)
+
 			return err
 		}
 
+		tracef("finding flag from error %[1]q", err)
+
 		trimmed, trimErr := flagFromError(err)
 		if trimErr != nil {
 			return err
 		}
 
-		// regenerate the initial args with the split short opts
+		tracef("regenerating the initial args with the split short opts")
+
 		argsWereSplit := false
 		for i, arg := range args {
-			// skip args that are not part of the error message
+			tracef("skipping args that are not part of the error message (i=%[1]v arg=%[2]q)", i, arg)
+
 			if name := strings.TrimLeft(arg, "-"); name != trimmed {
 				continue
 			}
 
-			// if we can't split, the error was accurate
+			tracef("trying to split short option (arg=%[1]q)", arg)
+
 			shortOpts := splitShortOptions(set, arg)
 			if len(shortOpts) == 1 {
 				return err
 			}
 
-			// swap current argument with the split version
+			tracef(
+				"swapping current argument with the split version (shortOpts=%[1]q args=%[2]q)",
+				shortOpts, args,
+			)
+
 			// do not include args that parsed correctly so far as it would
 			// trigger Value.Set() on those args and would result in
 			// duplicates for slice type flags
@@ -54,8 +70,9 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple
 			break
 		}
 
-		// This should be an impossible to reach code path, but in case the arg
-		// splitting failed to happen, this will prevent infinite loops
+		tracef("this should be an impossible to reach code path")
+		// but in case the arg splitting failed to happen, this
+		// will prevent infinite loops
 		if !argsWereSplit {
 			return err
 		}
diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt
index 82aa6f4c8c..5229697ea0 100644
--- a/testdata/godoc-v3.x.txt
+++ b/testdata/godoc-v3.x.txt
@@ -156,11 +156,11 @@ var HelpPrinterCustom helpPrinterCustom = printHelpCustom
 
 FUNCTIONS
 
-func DefaultAppComplete(cCtx *Context)
+func DefaultAppComplete(ctx context.Context, cmd *Command)
     DefaultAppComplete prints the list of subcommands as the default app
     completion method
 
-func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context)
+func DefaultCompleteWithFlags(cmd *Command) func(ctx context.Context, cmd *Command)
 func FlagNames(name string, aliases []string) []string
 func HandleExitCoder(err error)
     HandleExitCoder handles errors implementing ExitCoder by printing their
@@ -172,42 +172,42 @@ func HandleExitCoder(err error)
 
     This function is the default error-handling behavior for an App.
 
-func ShowAppHelp(cCtx *Context) error
+func ShowAppHelp(cmd *Command) error
     ShowAppHelp is an action that displays the help.
 
-func ShowAppHelpAndExit(c *Context, exitCode int)
+func ShowAppHelpAndExit(cmd *Command, exitCode int)
     ShowAppHelpAndExit - Prints the list of subcommands for the app and exits
     with exit code.
 
-func ShowCommandHelp(cCtx *Context, commandName string) error
+func ShowCommandHelp(ctx context.Context, cmd *Command, commandName string) error
     ShowCommandHelp prints help for the given command
 
-func ShowCommandHelpAndExit(c *Context, command string, code int)
+func ShowCommandHelpAndExit(ctx context.Context, cmd *Command, command string, code int)
     ShowCommandHelpAndExit - exits with code after showing help
 
-func ShowSubcommandHelp(cCtx *Context) error
+func ShowSubcommandHelp(cmd *Command) error
     ShowSubcommandHelp prints help for the given subcommand
 
-func ShowSubcommandHelpAndExit(c *Context, exitCode int)
+func ShowSubcommandHelpAndExit(cmd *Command, exitCode int)
     ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits
     with exit code.
 
-func ShowVersion(cCtx *Context)
+func ShowVersion(cmd *Command)
     ShowVersion prints the version number of the App
 
 
 TYPES
 
-type ActionFunc func(*Context) error
+type ActionFunc func(context.Context, *Command) error
     ActionFunc is the action to execute when no subcommands are specified
 
 type ActionableFlag interface {
-	RunAction(*Context) error
+	RunAction(context.Context, *Command) error
 }
     ActionableFlag is an interface that wraps Flag interface and RunAction
     operation.
 
-type AfterFunc func(*Context) error
+type AfterFunc func(context.Context, *Command) error
     AfterFunc is an action that executes after any subcommands are run and have
     finished. The AfterFunc is run even if Action() panics.
 
@@ -227,7 +227,7 @@ type Args interface {
 	Slice() []string
 }
 
-type BeforeFunc func(*Context) error
+type BeforeFunc func(context.Context, *Command) error
     BeforeFunc is an action that executes prior to any subcommands being run
     once the context is ready. If a non-nil error is returned, no subcommands
     are run.
@@ -258,7 +258,7 @@ func (s *BoolWithInverseFlag) IsSet() bool
 
 func (s *BoolWithInverseFlag) Names() []string
 
-func (s *BoolWithInverseFlag) RunAction(ctx *Context) error
+func (s *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error
 
 func (s *BoolWithInverseFlag) String() string
     Example for BoolFlag{Name: "env"} --env (default: false) || --no-env
@@ -379,8 +379,29 @@ type Command struct {
     string slice of arguments such as os.Args. A given Command may contain Flags
     and sub-commands in Commands.
 
+func (cmd *Command) Args() Args
+    Args returns the command line arguments associated with the command.
+
+func (cmd *Command) Bool(name string) bool
+
 func (cmd *Command) Command(name string) *Command
 
+func (cmd *Command) Count(name string) int
+    Count returns the num of occurrences of this flag
+
+func (cmd *Command) Duration(name string) time.Duration
+
+func (cmd *Command) FlagNames() []string
+    FlagNames returns a slice of flag names used by the this command and all of
+    its parent commands.
+
+func (cmd *Command) Float(name string) float64
+    Int looks up the value of a local IntFlag, returns 0 if not found
+
+func (cmd *Command) FloatSlice(name string) []float64
+    FloatSlice looks up the value of a local FloatSliceFlag, returns nil if not
+    found
+
 func (cmd *Command) FullName() string
     FullName returns the full name of the command. For commands with parents
     this ensures that the parent commands are part of the command path.
@@ -388,21 +409,70 @@ func (cmd *Command) FullName() string
 func (cmd *Command) HasName(name string) bool
     HasName returns true if Command.Name matches given name
 
+func (cmd *Command) Int(name string) int64
+    Int64 looks up the value of a local Int64Flag, returns 0 if not found
+
+func (cmd *Command) IntSlice(name string) []int64
+    IntSlice looks up the value of a local IntSliceFlag, returns nil if not
+    found
+
+func (cmd *Command) IsSet(name string) bool
+    IsSet determines if the flag was actually set
+
+func (cmd *Command) Lineage() []*Command
+    Lineage returns *this* command and all of its ancestor commands in order
+    from child to parent
+
+func (cmd *Command) LocalFlagNames() []string
+    LocalFlagNames returns a slice of flag names used in this command.
+
+func (cmd *Command) NArg() int
+    NArg returns the number of the command line arguments.
+
 func (cmd *Command) Names() []string
     Names returns the names including short names and aliases.
 
+func (cmd *Command) NumFlags() int
+    NumFlags returns the number of flags set
+
 func (cmd *Command) Root() *Command
     Root returns the Command at the root of the graph
 
-func (cmd *Command) Run(ctx context.Context, arguments []string) (deferErr error)
+func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error)
     Run is the entry point to the command graph. The positional arguments are
     parsed according to the Flag and Command definitions and the matching Action
     functions are run.
 
+func (cmd *Command) Set(name, value string) error
+    Set sets a context flag to a value.
+
+func (cmd *Command) String(name string) string
+
+func (cmd *Command) StringMap(name string) map[string]string
+    StringMap looks up the value of a local StringMapFlag, returns nil if not
+    found
+
+func (cmd *Command) StringSlice(name string) []string
+    StringSlice looks up the value of a local StringSliceFlag, returns nil if
+    not found
+
+func (cmd *Command) Timestamp(name string) *time.Time
+    Timestamp gets the timestamp from a flag name
+
 func (cmd *Command) ToFishCompletion() (string, error)
     ToFishCompletion creates a fish completion string for the `*App` The
     function errors if either parsing or writing of the string fails.
 
+func (cmd *Command) Uint(name string) uint64
+    Uint looks up the value of a local Uint64Flag, returns 0 if not found
+
+func (cmd *Command) UintSlice(name string) []uint64
+    UintSlice looks up the value of a local UintSliceFlag, returns nil if not
+    found
+
+func (cmd *Command) Value(name string) interface{}
+    Value returns the value of the flag corresponding to `name`
+
 func (cmd *Command) VisibleCategories() []CommandCategory
     VisibleCategories returns a slice of categories and commands that are
     Hidden=false
@@ -433,92 +503,9 @@ type CommandCategory interface {
 }
     CommandCategory is a category containing commands.
 
-type CommandNotFoundFunc func(*Context, string)
+type CommandNotFoundFunc func(context.Context, *Command, string)
     CommandNotFoundFunc is executed if the proper command cannot be found
 
-type Context struct {
-	context.Context
-	Command *Command
-
-	// Has unexported fields.
-}
-    Context is a type that is passed through to each Handler action in a cli
-    application. Context can be used to retrieve context-specific args and
-    parsed command-line options.
-
-func NewContext(cmd *Command, set *flag.FlagSet, parent *Context) *Context
-    NewContext creates a new context. For use in when invoking a Command action.
-
-func (cCtx *Context) Args() Args
-    Args returns the command line arguments associated with the context.
-
-func (cCtx *Context) Bool(name string) bool
-
-func (cCtx *Context) Count(name string) int
-    Count returns the num of occurrences of this flag
-
-func (cCtx *Context) Duration(name string) time.Duration
-
-func (cCtx *Context) FlagNames() []string
-    FlagNames returns a slice of flag names used by the this context and all of
-    its parent contexts.
-
-func (cCtx *Context) Float(name string) float64
-    Int looks up the value of a local IntFlag, returns 0 if not found
-
-func (cCtx *Context) FloatSlice(name string) []float64
-    FloatSlice looks up the value of a local FloatSliceFlag, returns nil if not
-    found
-
-func (cCtx *Context) Int(name string) int64
-    Int64 looks up the value of a local Int64Flag, returns 0 if not found
-
-func (cCtx *Context) IntSlice(name string) []int64
-    IntSlice looks up the value of a local IntSliceFlag, returns nil if not
-    found
-
-func (cCtx *Context) IsSet(name string) bool
-    IsSet determines if the flag was actually set
-
-func (cCtx *Context) Lineage() []*Context
-    Lineage returns *this* context and all of its ancestor contexts in order
-    from child to parent
-
-func (cCtx *Context) LocalFlagNames() []string
-    LocalFlagNames returns a slice of flag names used in this context.
-
-func (cCtx *Context) NArg() int
-    NArg returns the number of the command line arguments.
-
-func (cCtx *Context) NumFlags() int
-    NumFlags returns the number of flags set
-
-func (cCtx *Context) Set(name, value string) error
-    Set sets a context flag to a value.
-
-func (cCtx *Context) String(name string) string
-
-func (cCtx *Context) StringMap(name string) map[string]string
-    StringMap looks up the value of a local StringMapFlag, returns nil if not
-    found
-
-func (cCtx *Context) StringSlice(name string) []string
-    StringSlice looks up the value of a local StringSliceFlag, returns nil if
-    not found
-
-func (cCtx *Context) Timestamp(name string) *time.Time
-    Timestamp gets the timestamp from a flag name
-
-func (cCtx *Context) Uint(name string) uint64
-    Uint looks up the value of a local Uint64Flag, returns 0 if not found
-
-func (cCtx *Context) UintSlice(name string) []uint64
-    UintSlice looks up the value of a local UintSliceFlag, returns nil if not
-    found
-
-func (cCtx *Context) Value(name string) interface{}
-    Value returns the value of the flag corresponding to `name`
-
 type Countable interface {
 	Count() int
 }
@@ -576,7 +563,7 @@ func Exit(message interface{}, exitCode int) ExitCoder
     can be avoided by overriding the ExitErrHandler function on an App or the
     package-global OsExiter function.
 
-type ExitErrHandlerFunc func(cCtx *Context, err error)
+type ExitErrHandlerFunc func(context.Context, *Command, error)
     ExitErrHandlerFunc is executed if provided in order to handle exitError
     values returned by Actions and Before/After functions.
 
@@ -638,7 +625,7 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 
 	TakesFile bool // whether this flag takes a file argument, mainly for shell completion purposes
 
-	Action func(*Context, T) error // Action callback to be called when flag is set
+	Action func(context.Context, *Command, T) error // Action callback to be called when flag is set
 
 	Config C // Additional/Custom configuration associated with this flag type
 
@@ -658,8 +645,8 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error
     Apply populates the flag given the flag set and environment
 
-func (f *FlagBase[T, C, V]) Get(ctx *Context) T
-    Get returns the flag’s value in the given Context.
+func (f *FlagBase[T, C, V]) Get(cmd *Command) T
+    Get returns the flag’s value in the given Command.
 
 func (f *FlagBase[T, C, V]) GetCategory() string
     GetCategory returns the category of the flag
@@ -696,7 +683,7 @@ func (f *FlagBase[T, C, V]) IsVisible() bool
 func (f *FlagBase[T, C, V]) Names() []string
     Names returns the names of the flag
 
-func (f *FlagBase[T, C, V]) RunAction(ctx *Context) error
+func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error
     RunAction executes flag action if set
 
 func (f *FlagBase[T, C, V]) String() string
@@ -771,7 +758,7 @@ type IntegerConfig struct {
 }
     IntegerConfig is the configuration for all integer type flags
 
-type InvalidFlagAccessFunc func(*Context, string)
+type InvalidFlagAccessFunc func(context.Context, *Command, string)
     InvalidFlagAccessFunc is executed when an invalid flag is accessed from the
     context.
 
@@ -822,7 +809,7 @@ type MutuallyExclusiveFlags struct {
 type NoConfig struct{}
     NoConfig is for flags which dont need a custom configuration
 
-type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error
+type OnUsageErrorFunc func(ctx context.Context, cmd *Command, err error, isSubcommand bool) error
     OnUsageErrorFunc is executed if a usage error occurs. This is useful for
     displaying customized usage error messages. This function is able to replace
     the original error messages. If this function is not set, the "Incorrect
@@ -847,7 +834,7 @@ type Serializer interface {
 }
     Serializer is used to circumvent the limitations of flag.FlagSet.Set
 
-type ShellCompleteFunc func(*Context)
+type ShellCompleteFunc func(context.Context, *Command)
     ShellCompleteFunc is an action to execute when the shell completion flag is
     set