Repository: tox-dev/tox

Issue:
tox4  requires "--" separator for posargs, while tox 3 didn't
In tox3, positional arguments are passed to the command even when they are not separated from the tox arguments by a double dash.
In tox4, they are reported as unrecognized arguments.

Minimal example:
```
$ cat tox.ini
[tox]
skipsdist = True

[testenv]
allowlist_externals = echo
commands =
    echo {posargs:default}
```

New behavior:
```
$ tox4 foo bar
usage: tox [-h] [--colored {yes,no}] [-v | -q] [-c file] [--workdir dir] [--root dir] [--runner {virtualenv}] [--version] [--no-recreate-provision] [-r] [-x OVERRIDE]
           {run,r,run-parallel,p,depends,de,list,l,devenv,d,config,c,quickstart,q,exec,e,legacy,le} ...
tox: error: unrecognized arguments: foo bar
```

The same happens with `tox4 legacy foo bar`.

Old behavior:
```
$ tox foo bar
python run-test-pre: PYTHONHASHSEED='753'
python run-test: commands[0] | echo foo bar
foo bar
```

I am using tox4.0.0a8.

Debatable whether this is only works accidentally in tox3, because the behavior is not documented.
Now that tox4 relies on positional arguments itself, there is probably no reasonable way to support this.
However, I might be not the only one that has used this previously because it makes things like `tox -e release major` convenient. So, a short hint along the lines of "did you try to pass arguments to a command?" or a prominent hint in the documentation might be helpful.

Discussion:

Patch:
diff --git a/docs/changelog/2183.feature.rst b/docs/changelog/2183.feature.rst
new file mode 100644
index 00000000..9ddfb8cf
--- /dev/null
+++ b/docs/changelog/2183.feature.rst
@@ -0,0 +1,2 @@
+Display a hint for unrecognized argument CLI parse failures to use ``--`` separator to pass arguments to commands
+- by :user:`gaborbernat`.
diff --git a/src/tox/config/cli/parser.py b/src/tox/config/cli/parser.py
index a7a4e548..a49ce18b 100644
--- a/src/tox/config/cli/parser.py
+++ b/src/tox/config/cli/parser.py
@@ -76,6 +76,19 @@ class ArgumentParserWithEnvAndConfig(ArgumentParser):
                 raise TypeError(action)
         return of_type
 
+    def parse_args(  # type: ignore # avoid defining all overloads
+        self,
+        args: Sequence[str] | None = None,
+        namespace: Namespace | None = None,
+    ) -> Namespace:
+        res, argv = self.parse_known_args(args, namespace)
+        if argv:
+            self.error(
+                f'unrecognized arguments: {" ".join(argv)}\n'
+                "hint: if you tried to pass arguments to a command use -- to separate them from tox ones",
+            )
+        return res
+
 
 class HelpFormatter(ArgumentDefaultsHelpFormatter):
     """


Test patch:
diff --git a/tests/config/cli/test_parser.py b/tests/config/cli/test_parser.py
index 71d515cf..3b965d0d 100644
--- a/tests/config/cli/test_parser.py
+++ b/tests/config/cli/test_parser.py
@@ -7,7 +7,7 @@ import pytest
 from pytest_mock import MockerFixture
 
 from tox.config.cli.parser import Parsed, ToxParser
-from tox.pytest import MonkeyPatch
+from tox.pytest import CaptureFixture, MonkeyPatch
 
 
 def test_parser_const_with_default_none(monkeypatch: MonkeyPatch) -> None:
@@ -81,3 +81,11 @@ def test_parse_known_args_not_set(mocker: MockerFixture) -> None:
     parser = ToxParser.base()
     _, unknown = parser.parse_known_args(None)
     assert unknown == ["--help"]
+
+
+def test_parser_hint(capsys: CaptureFixture) -> None:
+    parser = ToxParser.base()
+    with pytest.raises(SystemExit):
+        parser.parse_args("foo")
+    out, err = capsys.readouterr()
+    assert err.endswith("hint: if you tried to pass arguments to a command use -- to separate them from tox ones\n")