From 8fc27f4d6931e15c8e67873cad191b5f118a94fd Mon Sep 17 00:00:00 2001 From: core-fe Date: Thu, 21 May 2026 00:19:56 -0700 Subject: [PATCH] ci: compensate cancelled push status noise --- .gitea/scripts/status-reaper.py | 36 ++++++++++++++++++++++++++--- tests/test_status_reaper.py | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/.gitea/scripts/status-reaper.py b/.gitea/scripts/status-reaper.py index 3c32eb6f..5bf4c7d5 100644 --- a/.gitea/scripts/status-reaper.py +++ b/.gitea/scripts/status-reaper.py @@ -47,7 +47,9 @@ What this script does, per `.gitea/workflows/status-reaper.yml` invocation: Parse context as ` / (push)`. Look up workflow_name in the trigger map: - missing → log ::notice:: and skip (conservative). - - has_push_trigger=True → preserve (real defect signal). + - has_push_trigger=True and description == "Has been cancelled" + → compensate cancelled/superseded push noise. + - has_push_trigger=True otherwise → preserve (real defect signal). - has_push_trigger=False → POST a compensating `state=success` status to /statuses/{sha} with the same context (Gitea de-dups by context) and a description @@ -141,6 +143,11 @@ PR_SHADOW_COMPENSATION_DESCRIPTION = ( "shadowed by successful push status on same SHA; see " ".gitea/scripts/status-reaper.py)" ) +CANCELLED_PUSH_COMPENSATION_DESCRIPTION = ( + "Compensated by status-reaper (push run was cancelled/superseded; " + "Gitea 1.22.6 reports cancelled runs as failure statuses)" +) +CANCELLED_DESCRIPTION = "Has been cancelled" # Context suffix the reaper acts on. Gitea hardcodes this for ALL # default-branch workflow runs. @@ -476,7 +483,7 @@ def reap( {compensated, preserved_real_push, preserved_unknown, preserved_non_failure, preserved_non_push_suffix, preserved_unparseable, compensated_pr_shadowed_by_push_success, - preserved_pr_without_push_success, + preserved_pr_without_push_success, compensated_cancelled_push, compensated_contexts: [, ...]} `compensated_contexts` is rev2-added so `reap_branch` can build @@ -490,6 +497,7 @@ def reap( "preserved_non_push_suffix": 0, "preserved_unparseable": 0, "compensated_pr_shadowed_by_push_success": 0, + "compensated_cancelled_push": 0, "preserved_pr_without_push_success": 0, "compensated_contexts": [], } @@ -567,8 +575,27 @@ def reap( counters["preserved_unknown"] += 1 continue + if (s.get("description") or "").strip() == CANCELLED_DESCRIPTION: + # Gitea 1.22.6 maps cancelled action runs to failure commit + # statuses. During merge bursts, older push runs can be + # superseded and cancelled even though a newer run for the + # same branch is the real signal. Compensate only the exact + # Gitea cancellation description; real push failures remain red. + post_compensating_status( + sha, + context, + s.get("target_url"), + description=CANCELLED_PUSH_COMPENSATION_DESCRIPTION, + dry_run=dry_run, + ) + counters["compensated"] += 1 + counters["compensated_cancelled_push"] += 1 + counters["compensated_contexts"].append(context) + continue + if workflow_trigger_map[workflow_name]: - # Real push trigger → real defect signal. Preserve. + # Real push trigger with a non-cancelled failure description + # remains a defect signal. Preserve. counters["preserved_real_push"] += 1 continue @@ -674,6 +701,7 @@ def reap_branch( "preserved_non_push_suffix": 0, "preserved_unparseable": 0, "compensated_pr_shadowed_by_push_success": 0, + "compensated_cancelled_push": 0, "preserved_pr_without_push_success": 0, "compensated_per_sha": {}, "skipped": True, @@ -689,6 +717,7 @@ def reap_branch( "preserved_non_push_suffix": 0, "preserved_unparseable": 0, "compensated_pr_shadowed_by_push_success": 0, + "compensated_cancelled_push": 0, "preserved_pr_without_push_success": 0, "compensated_per_sha": {}, } @@ -728,6 +757,7 @@ def reap_branch( "preserved_non_push_suffix", "preserved_unparseable", "compensated_pr_shadowed_by_push_success", + "compensated_cancelled_push", "preserved_pr_without_push_success", ): aggregate[key] += per_sha[key] diff --git a/tests/test_status_reaper.py b/tests/test_status_reaper.py index d7e4ed1f..f6eb7cf9 100644 --- a/tests/test_status_reaper.py +++ b/tests/test_status_reaper.py @@ -442,6 +442,46 @@ def test_reap_preserves_real_push(sr_module, monkeypatch): assert calls == [] # NO POST +def test_reap_compensates_cancelled_real_push_status(sr_module, monkeypatch): + """Gitea 1.22.6 maps cancelled push runs to failure statuses. + + A real push workflow with description exactly "Has been cancelled" + is cancel-cascade noise, not a defect signal. Status-reaper should + compensate it even though the workflow has a push trigger. + """ + calls = [] + + def fake_api(method, path, *, body=None, query=None, expect_json=True): + calls.append((method, path, body)) + return (201, {}) + + monkeypatch.setattr(sr_module, "api", fake_api) + + workflow_map = {"ci": True} + combined = { + "state": "failure", + "statuses": [ + { + "context": "ci / test (push)", + "status": "failure", + "description": "Has been cancelled", + "target_url": "https://example.test/actions/runs/1", + } + ], + } + + counters = sr_module.reap(workflow_map, combined, SHA, dry_run=False) + + assert counters["compensated"] == 1 + assert counters["compensated_cancelled_push"] == 1 + assert counters["preserved_real_push"] == 0 + assert len(calls) == 1 + assert calls[0][0] == "POST" + assert calls[0][1] == f"/repos/owner/repo/statuses/{SHA}" + assert calls[0][2]["context"] == "ci / test (push)" + assert calls[0][2]["state"] == "success" + + def test_reap_preserves_unknown_workflow(sr_module, monkeypatch, capsys): """Workflow not in map → ::notice:: + skip (conservative).""" monkeypatch.setattr(