From 00fcb4c93a41f452285339b7d877975fcbe008f8 Mon Sep 17 00:00:00 2001 From: yoni Date: Mon, 28 Sep 2026 12:41:23 +0000 Subject: [PATCH] fix: classify distinct check failures correctly --- strix/fix/prepare.py | 12 +++++++--- tests/test_fix_preparation.py | 42 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/strix/fix/prepare.py b/strix/fix/prepare.py index ecbe1562e..8b2c4d0bb 100644 --- a/strix/fix/prepare.py +++ b/strix/fix/prepare.py @@ -430,6 +430,14 @@ def _verification_gaps( return list(dict.fromkeys(gaps)) +def _matches_baseline_failure(result: CheckResult) -> bool: + if result.baseline_status is not CheckStatus.FAILED: + return False + candidate_output = " ".join(result.output.split()) + baseline_output = " ".join((result.baseline_output or "").split()) + return bool(candidate_output and candidate_output == baseline_output) + + async def prepare_fix( # noqa: PLR0915 request: FixPreparationRequestV1, workspace: Path, @@ -593,9 +601,7 @@ async def prepare_fix( # noqa: PLR0915 ) failed = [result for result in required if result.status is CheckStatus.FAILED] - baseline_failures = [ - result for result in failed if result.baseline_status is CheckStatus.FAILED - ] + baseline_failures = [result for result in failed if _matches_baseline_failure(result)] if baseline_failures: blocker = PreparationBlocker( kind=BlockerKind.REPOSITORY_BASELINE, diff --git a/tests/test_fix_preparation.py b/tests/test_fix_preparation.py index 5b9fac767..4cb781772 100644 --- a/tests/test_fix_preparation.py +++ b/tests/test_fix_preparation.py @@ -420,6 +420,48 @@ async def test_repository_baseline_failure_is_a_typed_blocker( assert result.attempts == 1 +@pytest.mark.asyncio +async def test_different_candidate_and_baseline_failures_are_repairable( + tmp_path: Path, +) -> None: + workspace, commit = _workspace(tmp_path) + repairs = 0 + + async def repair( + _context: PreparationContext, + _feedback: list[CheckResult], + ) -> RepairOutcome: + nonlocal repairs + repairs += 1 + return await _noop_repair(_context, _feedback) + + async def runner(_workspace: Path, command: CommandSpec) -> CheckResult: + return CheckResult( + name=command.name, + argv=command.argv, + status=CheckStatus.FAILED, + exit_code=1, + duration_seconds=0, + output="candidate-specific failure", + required=command.required, + baseline_status=CheckStatus.FAILED, + baseline_output="different pre-existing failure", + ) + + result = await prepare_fix( + _request(_candidate(commit)), + workspace, + repair=repair, + verify=_verified, + command_runner=runner, + ) + + assert result.state is PreparationState.FAILED + assert result.blocker is None + assert result.attempts == 2 + assert repairs == 2 + + @pytest.mark.asyncio async def test_unavailable_required_check_is_a_typed_blocker(tmp_path: Path) -> None: workspace, commit = _workspace(tmp_path)