From 212b59f16d8175db79a8ef881910f114cc970ef3 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 20:16:44 +0200 Subject: [PATCH 1/6] fix(install): fail positional total validation Return exit code 1 when every positional package fails validation, matching manifest installs and the documented exit contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/apm_cli/commands/install.py | 2 +- tests/unit/test_install_command.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/apm_cli/commands/install.py b/src/apm_cli/commands/install.py index e90d07c86..20b99d031 100644 --- a/src/apm_cli/commands/install.py +++ b/src/apm_cli/commands/install.py @@ -1523,7 +1523,7 @@ def install( # noqa: PLR0913 ) # Short-circuit: all packages failed validation -- nothing to install if outcome.all_failed: - return + sys.exit(1) # Note: Empty validated_packages is OK if packages are already in apm.yml; # only_packages is derived from validation outcomes below. diff --git a/tests/unit/test_install_command.py b/tests/unit/test_install_command.py index 825b61aa0..601ed63f9 100644 --- a/tests/unit/test_install_command.py +++ b/tests/unit/test_install_command.py @@ -337,6 +337,16 @@ def test_install_auto_created_apm_yml_has_correct_metadata( assert "description" in config assert "APM project" in config["description"] + @patch("apm_cli.commands.install._validate_package_exists", return_value=False) + def test_install_positional_url_total_validation_failure_exits_one(self, mock_validate): + """A positional URL that cannot be validated must fail the command.""" + with self._chdir_tmp(): + result = self.runner.invoke(cli, ["install", "https://127.0.0.1:1/org/repo.git"]) + + assert result.exit_code == 1 + assert "All packages failed validation" in result.output + mock_validate.assert_called_once() + @patch("apm_cli.commands.install._validate_package_exists") def test_install_invalid_package_format_with_no_apm_yml(self, mock_validate): """Test that invalid package format fails gracefully even with auto-bootstrap.""" From 518bad9dc9786837128ba80607be95fe1baee959 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 20:58:17 +0200 Subject: [PATCH 2/6] fix(install): avoid interrupted summary on validation failure Mark the validation summary as rendered before exiting so a clean total-validation failure does not append a misleading interruption warning. Addresses the CLI logging panel follow-up. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/apm_cli/commands/install.py | 1 + tests/unit/test_install_command.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/apm_cli/commands/install.py b/src/apm_cli/commands/install.py index 20b99d031..bcb08c318 100644 --- a/src/apm_cli/commands/install.py +++ b/src/apm_cli/commands/install.py @@ -1523,6 +1523,7 @@ def install( # noqa: PLR0913 ) # Short-circuit: all packages failed validation -- nothing to install if outcome.all_failed: + summary_rendered = True sys.exit(1) # Note: Empty validated_packages is OK if packages are already in apm.yml; # only_packages is derived from validation outcomes below. diff --git a/tests/unit/test_install_command.py b/tests/unit/test_install_command.py index 601ed63f9..c1eb48623 100644 --- a/tests/unit/test_install_command.py +++ b/tests/unit/test_install_command.py @@ -345,6 +345,7 @@ def test_install_positional_url_total_validation_failure_exits_one(self, mock_va assert result.exit_code == 1 assert "All packages failed validation" in result.output + assert "Install interrupted" not in result.output mock_validate.assert_called_once() @patch("apm_cli.commands.install._validate_package_exists") From fb904c0cb5404d66ca16bc1e299bf6eda4014b6b Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 20:58:17 +0200 Subject: [PATCH 3/6] test(install): tighten total-validation exit assertion Make the existing integration test require exit code 1 so it traps the positional total-failure regression instead of passing on error text alone. Addresses the test coverage panel follow-up. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/integration/test_wave7_marketplace_install_coverage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_wave7_marketplace_install_coverage.py b/tests/integration/test_wave7_marketplace_install_coverage.py index d8ec7718b..dc3bb000e 100644 --- a/tests/integration/test_wave7_marketplace_install_coverage.py +++ b/tests/integration/test_wave7_marketplace_install_coverage.py @@ -1056,8 +1056,8 @@ def test_install_package_validation_failure(self, runner: CliRunner, tmp_path: P ), ): result = runner.invoke(cli, ["install", "owner/nonexistent"]) - # All validations failed → exit - assert result.exit_code != 0 or "not accessible" in result.output + # All validations failed, so the command must report failure. + assert result.exit_code == 1 # =========================================================================== From da9b48d90acbddedf0780474ae4dba8cbee60033 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 20:58:17 +0200 Subject: [PATCH 4/6] docs(changelog): note positional install exit fix Record the corrected positional total-failure exit status under Unreleased so CI users can identify the reliability fix. Addresses the OSS growth panel follow-up. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f7a888f0..47aa3f8e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Positional `apm install ` now exits non-zero when all packages fail + validation, matching manifest installs for reliable CI scripting. (#2131) + ## [0.24.1] - 2026-07-10 ### Fixed From 969ccc909703ce7cd1518d74ca5c621fe27dab82 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 21:05:01 +0200 Subject: [PATCH 5/6] test(install): loosen validator call-count coupling Assert that validation ran without pinning the implementation to exactly one probe, preserving the user-visible exit and message contract. Addresses Copilot inline comment 3561124008. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/unit/test_install_command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_install_command.py b/tests/unit/test_install_command.py index c1eb48623..d4f42aa94 100644 --- a/tests/unit/test_install_command.py +++ b/tests/unit/test_install_command.py @@ -346,7 +346,7 @@ def test_install_positional_url_total_validation_failure_exits_one(self, mock_va assert result.exit_code == 1 assert "All packages failed validation" in result.output assert "Install interrupted" not in result.output - mock_validate.assert_called_once() + mock_validate.assert_called() @patch("apm_cli.commands.install._validate_package_exists") def test_install_invalid_package_format_with_no_apm_yml(self, mock_validate): From dd86f66b2adc23fde952282a4d5d3dcab78a9b81 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Fri, 10 Jul 2026 21:12:41 +0200 Subject: [PATCH 6/6] chore: retrigger dropped PR checks GitHub did not register pull_request workflow runs for the previous synchronize event, so retrigger the CI webhook without changing the reviewed tree. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>