Repository: dotnet/command-line-api (2025-10-16T23:29:41Z)

Issue:
Option with default value does not have Action added to PreActions
Say there is an QuietOption which is a boolean and has some action associated with it.

If --quiet is specified on the command line, the associated action gets added to the PreAction list and is executed before the main command action.

If --quiet is not specified on the command line, but instead it is given a default value - say driven from configuration - then the associated action is not added to the PreAction list and is not executed.

As far as the parse result is concerned, both of these look the same and so should function the same, i.e. the --quiet option has been supplied somehow and action should happen accordingly. 


Discussion:
Edit: Nevermind. I messed up my local test run.

~This sounds like a pretty simple case but I realized after "fixing" it that the test passes even without the change in the library code. @barrygiles Would you mind taking a look at the PR and verifying that the test I added describes the issue you're seeing? I'm wondering whether the behavior was incidentally fixed at some point or the test is wrong.~




Patch:
diff --git a/src/System.CommandLine/Parsing/ParseOperation.cs b/src/System.CommandLine/Parsing/ParseOperation.cs
index 0ae9c4bcf3..6f17b13a74 100644
--- a/src/System.CommandLine/Parsing/ParseOperation.cs
+++ b/src/System.CommandLine/Parsing/ParseOperation.cs
@@ -342,6 +342,18 @@ private void AddPreAction(CommandLineAction action)
             _preActions.Add(action);
         }
 
+        private void AddPreActionsForImplicitOptions()
+        {
+            foreach (var kvp in _symbolResultTree)
+            {
+                if (kvp is { Key: Option { Action: { Terminating: false } action }, Value: OptionResult { Implicit: true } } && 
+                    _primaryAction != action)
+                {
+                    AddPreAction(action);
+                }
+            }
+        }
+
         private void AddCurrentTokenToUnmatched()
         {
             if (CurrentToken.Type == TokenType.DoubleDash)
@@ -435,6 +447,8 @@ private void ValidateAndAddDefaultResults()
                     }
                 }
             }
+
+            AddPreActionsForImplicitOptions();
         }
     }
 }
\ No newline at end of file


Test patch:
diff --git a/src/System.CommandLine.Tests/OptionTests.cs b/src/System.CommandLine.Tests/OptionTests.cs
index f898c25024..10ec1630d8 100644
--- a/src/System.CommandLine.Tests/OptionTests.cs
+++ b/src/System.CommandLine.Tests/OptionTests.cs
@@ -531,5 +531,27 @@ public void Default_value_is_used_when_option_with_ZeroOrOne_arity_is_parsed_wit
 
             parseResult.GetValue(option).Should().Be(42);
         }
+
+        [Fact] // https://github.com/dotnet/command-line-api/issues/2621
+        public void Option_with_default_value_has_Action_added_to_PreActions_when_not_specified_but_has_default()
+        {
+            var preactionWasInvoked = false;
+            var option = new Option<bool>("--quiet")
+            {
+                DefaultValueFactory = _ => true,
+                Action = new SynchronousTestAction(_ => preactionWasInvoked = true, terminating: false)
+            };
+
+            var rootCommand = new RootCommand
+            {
+                option
+            };
+            rootCommand.SetAction(_ => { });
+
+            var parseResult = rootCommand.Parse("");
+            parseResult.Invoke();
+
+            preactionWasInvoked.Should().BeTrue();
+        }
     }
 }