From 91a979d692f51881da6b598e6cd48542de639f72 Mon Sep 17 00:00:00 2001 From: siddmoparti Date: Wed, 18 Mar 2026 20:13:50 -0400 Subject: [PATCH 1/2] Mark subcommand optional for invoke_without_command groups --- repro.py | 14 ++++++++++++++ src/click/core.py | 2 ++ tests/test_commands.py | 17 +++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 repro.py diff --git a/repro.py b/repro.py new file mode 100644 index 0000000000..8ccfdfa6e7 --- /dev/null +++ b/repro.py @@ -0,0 +1,14 @@ +import click + +@click.group(invoke_without_command=True) +@click.pass_context +def main(ctx): + if ctx.invoked_subcommand is not None: + return + +@main.command() +def sub(): + pass + +if __name__ == "__main__": + main() diff --git a/src/click/core.py b/src/click/core.py index 6adc65ccd6..1e9173d5e9 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1585,6 +1585,8 @@ def __init__( if subcommand_metavar is None: if chain: subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + elif self.invoke_without_command: + subcommand_metavar = "[COMMAND] [ARGS]..." else: subcommand_metavar = "COMMAND [ARGS]..." diff --git a/tests/test_commands.py b/tests/test_commands.py index f26529a542..6bc78fa78f 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -298,6 +298,23 @@ def sync(): assert not result.exception assert result.output == "no subcommand, use default\nin subcommand\n" +def test_invoke_without_command_usage_shows_optional_subcommand(runner): + import click + + @click.group(invoke_without_command=True) + @click.pass_context + def cli(ctx): + if ctx.invoked_subcommand is not None: + return + + @cli.command() + def sub(): + pass + + result = runner.invoke(cli, ["--help"]) + assert result.exit_code == 0 + assert "Usage: cli [OPTIONS] [COMMAND] [ARGS]..." in result.output + def test_aliased_command_canonical_name(runner): class AliasedGroup(click.Group): From 6065e69bc249ab17bb1a38b1107e6f0dcbb8f14e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 00:39:07 +0000 Subject: [PATCH 2/2] [pre-commit.ci lite] apply automatic fixes --- repro.py | 3 +++ tests/test_commands.py | 1 + 2 files changed, 4 insertions(+) diff --git a/repro.py b/repro.py index 8ccfdfa6e7..4d2726c5ac 100644 --- a/repro.py +++ b/repro.py @@ -1,14 +1,17 @@ import click + @click.group(invoke_without_command=True) @click.pass_context def main(ctx): if ctx.invoked_subcommand is not None: return + @main.command() def sub(): pass + if __name__ == "__main__": main() diff --git a/tests/test_commands.py b/tests/test_commands.py index 6bc78fa78f..e367d38244 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -298,6 +298,7 @@ def sync(): assert not result.exception assert result.output == "no subcommand, use default\nin subcommand\n" + def test_invoke_without_command_usage_shows_optional_subcommand(runner): import click