[SCM] PostGIS branch master updated. 3.7.0beta2-117-g99f9ff539

git at osgeo.org git at osgeo.org
Sat Aug 22 12:31:23 PDT 2026


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".

The branch, master has been updated
       via  99f9ff5396e9ac19e5f66c7638f42668ca001d0e (commit)
       via  7f1a212e95a5680e217660cfdfa7215985b6abe6 (commit)
       via  448670d5b96dd07cf5bb1ba85c7cb9320638578d (commit)
       via  3d96989d303de93bb6c271420fe700accb441926 (commit)
       via  78c4f16f3d7b10c06cf5815794326fb1b0c830f3 (commit)
      from  b2d575041cbbb33402ed52c90284e93e7c649248 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 99f9ff5396e9ac19e5f66c7638f42668ca001d0e
Merge: b2d575041 7f1a212e9
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date:   Sat Aug 22 12:31:22 2026 -0700

    Merge pull request 'ci: do not report old running revisions as current' (!766) from Komzpa/postgis:fix/ci-current-running-revision-20260822 into master
    
    ## Summary
    
    - Mark an in-progress CI run from an older revision as `unknown`, rather than current running coverage.
    - Keep the revision-distance diagnostic and leave a current-head running job unchanged.
    
    ## Rationale
    
    A provider can still be executing an older checkout after the target branch advances. That run cannot establish the status of the current branch head.
    
    ## Tests
    
    - `python3 -m unittest utils.docs.tests.test_ci_status`
    
    Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/766


commit 7f1a212e95a5680e217660cfdfa7215985b6abe6
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sat Aug 22 22:45:36 2026 +0400

    Fail closed on revisionless running CI

diff --git a/utils/docs/ci_status/report.py b/utils/docs/ci_status/report.py
index 76fb9f42f..8f322c069 100644
--- a/utils/docs/ci_status/report.py
+++ b/utils/docs/ci_status/report.py
@@ -1463,32 +1463,36 @@ def stale_running_result(result, distance_count=None, distance_ref=None, detail=
     return stale
 
 
+def exact_head_distance(result, exact_head):
+    revision = result.get("revision")
+    branch_name = result.get("branch")
+    if not revision or not exact_head or not branch_name:
+        return None, None
+    return git_commit_distance(revision, exact_head), branch_name
+
+
 def apply_staleness(result, config, check, branch_heads=None):
     threshold = stale_after_hours(config, check)
-    distance_count, distance_ref = result_revision_distance(config, result)
     exact_head = None
     if branch_heads is not None:
         exact_head = branch_heads.get(result.get("branch"))
     exact_head_required = bool(config.get("cache_head_remote"))
-    if result["status"] == IN_PROGRESS and result.get("revision") and exact_head_required and not exact_head:
-        return stale_running_result(
-            result,
-            distance_count=distance_count,
-            distance_ref=distance_ref,
-            detail="branch head unavailable",
-        )
-    if (
-        result["status"] == IN_PROGRESS
-        and exact_head
-        and result.get("revision")
-        and result["revision"].lower() != exact_head.lower()
-    ):
-        return stale_running_result(
-            result,
-            distance_count=distance_count,
-            distance_ref=distance_ref,
-            detail=f"not at {result['branch']} head",
-        )
+    if result["status"] == IN_PROGRESS and exact_head_required:
+        if not result.get("revision"):
+            return stale_running_result(result, detail="running revision unavailable")
+        if not exact_head:
+            return stale_running_result(result, detail="branch head unavailable")
+        if result["revision"].lower() != exact_head.lower():
+            distance_count, distance_ref = exact_head_distance(result, exact_head)
+            return stale_running_result(
+                result,
+                distance_count=distance_count,
+                distance_ref=distance_ref,
+                detail=f"not at {result['branch']} head",
+            )
+        return result
+
+    distance_count, distance_ref = result_revision_distance(config, result)
     if result["status"] == IN_PROGRESS and distance_count and distance_count > 0:
         return stale_running_result(result, distance_count=distance_count, distance_ref=distance_ref)
     if result["status"] != IN_PROGRESS and distance_count and distance_count > 0:
diff --git a/utils/docs/tests/test_ci_status.py b/utils/docs/tests/test_ci_status.py
index bf2ad90e5..bb8d013d7 100644
--- a/utils/docs/tests/test_ci_status.py
+++ b/utils/docs/tests/test_ci_status.py
@@ -278,6 +278,80 @@ class CIStatusTest(unittest.TestCase):
         self.assertEqual("Stale running", result["status_label"])
         self.assertIn("branch head unavailable", result["message"])
 
+    def test_collect_status_running_result_fails_closed_when_revision_unavailable(self):
+        config = {
+            "cache_head_remote": "https://example.test/postgis.git",
+            "branches": [{"name": "master", "label": "master"}],
+            "checks": [{
+                "name": "Synthetic CI",
+                "provider": "synthetic",
+                "required": True,
+            }],
+        }
+        provider = mock.Mock(return_value={
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "badge: running",
+        })
+
+        with (
+            mock.patch.dict(CI_STATUS.PROVIDERS, {"synthetic": provider}),
+            mock.patch.object(CI_STATUS, "resolve_cache_heads", return_value={"master": "f" * 40}),
+        ):
+            data = CI_STATUS.collect_status(config)
+
+        result = data["branches"][0]["checks"][0]
+        self.assertEqual(CI_STATUS.UNKNOWN, result["status"])
+        self.assertEqual(CI_STATUS.IN_PROGRESS, result["stale_base_status"])
+        self.assertIn("running revision unavailable", result["message"])
+
+    def test_collect_status_running_mismatch_uses_canonical_sha_distance_only(self):
+        config = {
+            "cache_head_remote": "https://example.test/postgis.git",
+            "branches": [{"name": "master", "label": "master"}],
+            "checks": [{
+                "name": "Synthetic CI",
+                "provider": "synthetic",
+                "required": True,
+            }],
+        }
+        provider = mock.Mock(return_value={
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "revision": "e" * 40,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "build 8178; running: Winnie",
+        })
+
+        def fake_distance(revision, ref):
+            if ref == "b" * 40:
+                return 7
+            return 999
+
+        with (
+            mock.patch.dict(CI_STATUS.PROVIDERS, {"synthetic": provider}),
+            mock.patch.object(CI_STATUS, "git_commit_distance", side_effect=fake_distance),
+            mock.patch.object(
+                CI_STATUS,
+                "resolve_cache_heads",
+                return_value={"master": "b" * 40},
+            ),
+        ):
+            data = CI_STATUS.collect_status(config)
+
+        result = data["branches"][0]["checks"][0]
+        self.assertEqual(CI_STATUS.UNKNOWN, result["status"])
+        self.assertEqual(7, result["revision_commits_behind"])
+        self.assertEqual("7 commits behind master", result["revision_distance"])
+        self.assertIn("7 commits behind master", result["message"])
+
     def test_cache_head_lookup_queries_remote_once_for_all_branches(self):
         config = {"cache_head_remote": "https://example.test/postgis.git"}
         branches = [
@@ -518,6 +592,7 @@ class CIStatusTest(unittest.TestCase):
 
     def test_apply_staleness_marks_running_result_unknown_when_exact_head_differs(self):
         config = {
+            "cache_head_remote": "https://example.test/postgis.git",
             "stale_after_hours": 168,
             "branches": [{"name": "master", "label": "master"}],
         }
@@ -533,7 +608,7 @@ class CIStatusTest(unittest.TestCase):
             "message": "build 8178; running: Winnie",
         }
 
-        with mock.patch.object(CI_STATUS, "result_revision_distance", return_value=(0, "master")):
+        with mock.patch.object(CI_STATUS, "git_commit_distance", return_value=None):
             stale = CI_STATUS.apply_staleness(running, config, stale_check, {"master": "4" * 40})
 
         self.assertEqual(CI_STATUS.UNKNOWN, stale["status"])
@@ -542,6 +617,30 @@ class CIStatusTest(unittest.TestCase):
         self.assertNotIn("revision_distance", stale)
         self.assertIn("not at master head", stale["message"])
 
+    def test_apply_staleness_marks_running_result_unknown_when_revision_unavailable(self):
+        config = {
+            "cache_head_remote": "https://example.test/postgis.git",
+            "stale_after_hours": 168,
+            "branches": [{"name": "master", "label": "master"}],
+        }
+        stale_check = {"name": "Synthetic CI"}
+        running = {
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "badge: running",
+        }
+
+        stale = CI_STATUS.apply_staleness(running, config, stale_check, {"master": "4" * 40})
+
+        self.assertEqual(CI_STATUS.UNKNOWN, stale["status"])
+        self.assertEqual(CI_STATUS.IN_PROGRESS, stale["stale_base_status"])
+        self.assertEqual("Stale running", stale["status_label"])
+        self.assertIn("running revision unavailable", stale["message"])
+
     def test_github_actions_requests_history_for_the_named_workflow(self):
         check_config = {
             "name": "GitHub Actions / Linux",

commit 448670d5b96dd07cf5bb1ba85c7cb9320638578d
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sat Aug 22 22:41:07 2026 +0400

    Normalize canonical running SHA checks

diff --git a/utils/docs/ci_status/report.py b/utils/docs/ci_status/report.py
index 8cd3f9609..76fb9f42f 100644
--- a/utils/docs/ci_status/report.py
+++ b/utils/docs/ci_status/report.py
@@ -1449,6 +1449,20 @@ def stale_after_hours(config, check):
         raise ConfigError(f"invalid stale_after_hours for {check['name']}: {value}")
 
 
+def stale_running_result(result, distance_count=None, distance_ref=None, detail=None):
+    stale = dict(result)
+    stale["stale_base_status"] = result["status"]
+    stale["status"] = UNKNOWN
+    stale["status_label"] = "Stale running"
+    if distance_count and distance_count > 0:
+        stale["revision_commits_behind"] = distance_count
+        stale["revision_compare_ref"] = distance_ref
+        stale["revision_distance"] = revision_distance_text(distance_count, distance_ref)
+    message_detail = stale.get("revision_distance") or detail
+    stale["message"] = f"{result.get('message', 'CI run')} ({message_detail})"
+    return stale
+
+
 def apply_staleness(result, config, check, branch_heads=None):
     threshold = stale_after_hours(config, check)
     distance_count, distance_ref = result_revision_distance(config, result)
@@ -1457,44 +1471,26 @@ def apply_staleness(result, config, check, branch_heads=None):
         exact_head = branch_heads.get(result.get("branch"))
     exact_head_required = bool(config.get("cache_head_remote"))
     if result["status"] == IN_PROGRESS and result.get("revision") and exact_head_required and not exact_head:
-        stale = dict(result)
-        stale["stale_base_status"] = result["status"]
-        stale["status"] = UNKNOWN
-        stale["status_label"] = "Stale running"
-        if distance_count and distance_count > 0:
-            stale["revision_commits_behind"] = distance_count
-            stale["revision_compare_ref"] = distance_ref
-            stale["revision_distance"] = revision_distance_text(distance_count, distance_ref)
-        detail = stale.get("revision_distance") or "branch head unavailable"
-        stale["message"] = f"{result.get('message', 'CI run')} ({detail})"
-        return stale
+        return stale_running_result(
+            result,
+            distance_count=distance_count,
+            distance_ref=distance_ref,
+            detail="branch head unavailable",
+        )
     if (
         result["status"] == IN_PROGRESS
         and exact_head
         and result.get("revision")
-        and result["revision"] != exact_head
+        and result["revision"].lower() != exact_head.lower()
     ):
-        stale = dict(result)
-        stale["stale_base_status"] = result["status"]
-        stale["status"] = UNKNOWN
-        stale["status_label"] = "Stale running"
-        if distance_count and distance_count > 0:
-            stale["revision_commits_behind"] = distance_count
-            stale["revision_compare_ref"] = distance_ref
-            stale["revision_distance"] = revision_distance_text(distance_count, distance_ref)
-        detail = stale.get("revision_distance") or f"not at {result['branch']} head"
-        stale["message"] = f"{result.get('message', 'CI run')} ({detail})"
-        return stale
+        return stale_running_result(
+            result,
+            distance_count=distance_count,
+            distance_ref=distance_ref,
+            detail=f"not at {result['branch']} head",
+        )
     if result["status"] == IN_PROGRESS and distance_count and distance_count > 0:
-        stale = dict(result)
-        stale["revision_commits_behind"] = distance_count
-        stale["revision_compare_ref"] = distance_ref
-        stale["revision_distance"] = revision_distance_text(distance_count, distance_ref)
-        stale["stale_base_status"] = result["status"]
-        stale["status"] = UNKNOWN
-        stale["status_label"] = "Stale running"
-        stale["message"] = f"{result.get('message', 'CI run')} ({stale['revision_distance']})"
-        return stale
+        return stale_running_result(result, distance_count=distance_count, distance_ref=distance_ref)
     if result["status"] != IN_PROGRESS and distance_count and distance_count > 0:
         stale = dict(result)
         stale["revision_commits_behind"] = distance_count
diff --git a/utils/docs/tests/test_ci_status.py b/utils/docs/tests/test_ci_status.py
index bf95a427a..bf2ad90e5 100644
--- a/utils/docs/tests/test_ci_status.py
+++ b/utils/docs/tests/test_ci_status.py
@@ -492,6 +492,30 @@ class CIStatusTest(unittest.TestCase):
         self.assertNotIn("stale_base_status", current)
         self.assertNotIn("revision_distance", current)
 
+    def test_apply_staleness_keeps_running_result_at_branch_head_case_insensitively(self):
+        config = {
+            "stale_after_hours": 168,
+            "branches": [{"name": "master", "label": "master"}],
+        }
+        stale_check = {"name": "Synthetic CI"}
+        running = {
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "revision": "ab" * 20,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "build 8186; running: Winnie",
+        }
+
+        with mock.patch.object(CI_STATUS, "result_revision_distance", return_value=(0, "master")):
+            current = CI_STATUS.apply_staleness(running, config, stale_check, {"master": ("AB" * 20)})
+
+        self.assertEqual(CI_STATUS.IN_PROGRESS, current["status"])
+        self.assertNotIn("stale_base_status", current)
+        self.assertNotIn("revision_distance", current)
+
     def test_apply_staleness_marks_running_result_unknown_when_exact_head_differs(self):
         config = {
             "stale_after_hours": 168,

commit 3d96989d303de93bb6c271420fe700accb441926
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sat Aug 22 22:37:27 2026 +0400

    Use canonical heads for running CI rows

diff --git a/utils/docs/ci_status/report.py b/utils/docs/ci_status/report.py
index 19529ed40..8cd3f9609 100644
--- a/utils/docs/ci_status/report.py
+++ b/utils/docs/ci_status/report.py
@@ -1376,23 +1376,13 @@ def load_status_cache(path):
 
 
 def resolve_cache_heads(config, work, cache, timeout):
-    if not cache:
-        return {}
     remote = config.get("cache_head_remote")
     if not remote:
         return {}
     parsed = urllib.parse.urlparse(remote)
     if parsed.scheme not in ("https", "http") or not parsed.netloc or remote.startswith("-"):
         raise ConfigError("cache_head_remote must be an HTTP(S) URL")
-    branch_names = sorted({
-        branch["name"]
-        for branch, _check in work
-        if any(
-            cached.get("status") == SUCCESS
-            for (cached_branch, _cached_check), cached in cache["checks"].items()
-            if cached_branch == branch["name"]
-        )
-    })
+    branch_names = sorted({branch["name"] for branch, _check in work})
     if not branch_names:
         return {}
     refs = [f"refs/heads/{name}" for name in branch_names]
@@ -1459,9 +1449,42 @@ def stale_after_hours(config, check):
         raise ConfigError(f"invalid stale_after_hours for {check['name']}: {value}")
 
 
-def apply_staleness(result, config, check):
+def apply_staleness(result, config, check, branch_heads=None):
     threshold = stale_after_hours(config, check)
     distance_count, distance_ref = result_revision_distance(config, result)
+    exact_head = None
+    if branch_heads is not None:
+        exact_head = branch_heads.get(result.get("branch"))
+    exact_head_required = bool(config.get("cache_head_remote"))
+    if result["status"] == IN_PROGRESS and result.get("revision") and exact_head_required and not exact_head:
+        stale = dict(result)
+        stale["stale_base_status"] = result["status"]
+        stale["status"] = UNKNOWN
+        stale["status_label"] = "Stale running"
+        if distance_count and distance_count > 0:
+            stale["revision_commits_behind"] = distance_count
+            stale["revision_compare_ref"] = distance_ref
+            stale["revision_distance"] = revision_distance_text(distance_count, distance_ref)
+        detail = stale.get("revision_distance") or "branch head unavailable"
+        stale["message"] = f"{result.get('message', 'CI run')} ({detail})"
+        return stale
+    if (
+        result["status"] == IN_PROGRESS
+        and exact_head
+        and result.get("revision")
+        and result["revision"] != exact_head
+    ):
+        stale = dict(result)
+        stale["stale_base_status"] = result["status"]
+        stale["status"] = UNKNOWN
+        stale["status_label"] = "Stale running"
+        if distance_count and distance_count > 0:
+            stale["revision_commits_behind"] = distance_count
+            stale["revision_compare_ref"] = distance_ref
+            stale["revision_distance"] = revision_distance_text(distance_count, distance_ref)
+        detail = stale.get("revision_distance") or f"not at {result['branch']} head"
+        stale["message"] = f"{result.get('message', 'CI run')} ({detail})"
+        return stale
     if result["status"] == IN_PROGRESS and distance_count and distance_count > 0:
         stale = dict(result)
         stale["revision_commits_behind"] = distance_count
@@ -1538,12 +1561,12 @@ async def collect_status_async(config, selected_branch=None, include_eol=False,
         branch, check = item
         cached = cached_success_result(branch, check, cache, cache_heads)
         if cached:
-            return apply_staleness(cached, config, check)
+            return apply_staleness(cached, config, check, cache_heads)
         provider = PROVIDERS.get(check.get("provider"))
         if provider is None:
             raise ConfigError(f"unsupported provider for {check['name']}: {check.get('provider')}")
         try:
-            return apply_staleness(provider(check, branch, timeout), config, check)
+            return apply_staleness(provider(check, branch, timeout), config, check, cache_heads)
         except RECOVERABLE_PROVIDER_ERRORS as exc:
             return result_from_exception(check, branch, exc)
 
diff --git a/utils/docs/tests/test_ci_status.py b/utils/docs/tests/test_ci_status.py
index 345d8d4af..bf95a427a 100644
--- a/utils/docs/tests/test_ci_status.py
+++ b/utils/docs/tests/test_ci_status.py
@@ -164,6 +164,7 @@ class CIStatusTest(unittest.TestCase):
 
     def test_cache_uses_fresh_remote_head_not_stale_local_ref(self):
         config = {
+            "cache_head_remote": "https://example.test/postgis.git",
             "branches": [{"name": "master", "label": "master"}],
             "checks": [{
                 "name": "Synthetic CI",
@@ -203,6 +204,80 @@ class CIStatusTest(unittest.TestCase):
         provider.assert_called_once()
         self.assertEqual(CI_STATUS.FAILURE, data["branches"][0]["checks"][0]["status"])
 
+    def test_collect_status_exact_remote_head_marks_stale_running_unknown_despite_zero_distance(self):
+        config = {
+            "cache_head_remote": "https://example.test/postgis.git",
+            "branches": [{"name": "master", "label": "master"}],
+            "checks": [{
+                "name": "Synthetic CI",
+                "provider": "synthetic",
+                "required": True,
+            }],
+        }
+        provider = mock.Mock(return_value={
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "revision": "e" * 40,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "build 8178; running: Winnie",
+        })
+
+        with (
+            mock.patch.dict(CI_STATUS.PROVIDERS, {"synthetic": provider}),
+            mock.patch.object(CI_STATUS, "git_commit_distance", return_value=0),
+            mock.patch.object(
+                CI_STATUS,
+                "resolve_cache_heads",
+                return_value={"master": "b" * 40},
+            ),
+        ):
+            data = CI_STATUS.collect_status(config)
+
+        provider.assert_called_once()
+        result = data["branches"][0]["checks"][0]
+        self.assertEqual(CI_STATUS.UNKNOWN, result["status"])
+        self.assertEqual(CI_STATUS.IN_PROGRESS, result["stale_base_status"])
+        self.assertEqual("Stale running", result["status_label"])
+        self.assertNotIn("revision_distance", result)
+        self.assertIn("not at master head", result["message"])
+
+    def test_collect_status_running_result_fails_closed_when_branch_head_missing(self):
+        config = {
+            "cache_head_remote": "https://example.test/postgis.git",
+            "branches": [{"name": "master", "label": "master"}],
+            "checks": [{
+                "name": "Synthetic CI",
+                "provider": "synthetic",
+                "required": True,
+            }],
+        }
+        provider = mock.Mock(return_value={
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "revision": "f" * 40,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "build 8180; running: Winnie",
+        })
+
+        with (
+            mock.patch.dict(CI_STATUS.PROVIDERS, {"synthetic": provider}),
+            mock.patch.object(CI_STATUS, "git_commit_distance", return_value=0),
+            mock.patch.object(CI_STATUS, "resolve_cache_heads", return_value={}),
+        ):
+            data = CI_STATUS.collect_status(config)
+
+        result = data["branches"][0]["checks"][0]
+        self.assertEqual(CI_STATUS.UNKNOWN, result["status"])
+        self.assertEqual(CI_STATUS.IN_PROGRESS, result["stale_base_status"])
+        self.assertEqual("Stale running", result["status_label"])
+        self.assertIn("branch head unavailable", result["message"])
+
     def test_cache_head_lookup_queries_remote_once_for_all_branches(self):
         config = {"cache_head_remote": "https://example.test/postgis.git"}
         branches = [
@@ -411,12 +486,38 @@ class CIStatusTest(unittest.TestCase):
         }
 
         with mock.patch.object(CI_STATUS, "result_revision_distance", return_value=(0, "master")):
-            current = CI_STATUS.apply_staleness(running, config, stale_check)
+            current = CI_STATUS.apply_staleness(running, config, stale_check, {"master": "2" * 40})
 
         self.assertEqual(CI_STATUS.IN_PROGRESS, current["status"])
         self.assertNotIn("stale_base_status", current)
         self.assertNotIn("revision_distance", current)
 
+    def test_apply_staleness_marks_running_result_unknown_when_exact_head_differs(self):
+        config = {
+            "stale_after_hours": 168,
+            "branches": [{"name": "master", "label": "master"}],
+        }
+        stale_check = {"name": "Synthetic CI"}
+        running = {
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "revision": "3" * 40,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "build 8178; running: Winnie",
+        }
+
+        with mock.patch.object(CI_STATUS, "result_revision_distance", return_value=(0, "master")):
+            stale = CI_STATUS.apply_staleness(running, config, stale_check, {"master": "4" * 40})
+
+        self.assertEqual(CI_STATUS.UNKNOWN, stale["status"])
+        self.assertEqual(CI_STATUS.IN_PROGRESS, stale["stale_base_status"])
+        self.assertEqual("Stale running", stale["status_label"])
+        self.assertNotIn("revision_distance", stale)
+        self.assertIn("not at master head", stale["message"])
+
     def test_github_actions_requests_history_for_the_named_workflow(self):
         check_config = {
             "name": "GitHub Actions / Linux",

commit 78c4f16f3d7b10c06cf5815794326fb1b0c830f3
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sat Aug 22 22:29:21 2026 +0400

    Fix stale running CI revision handling

diff --git a/utils/docs/ci_status/report.py b/utils/docs/ci_status/report.py
index ac75c704e..19529ed40 100644
--- a/utils/docs/ci_status/report.py
+++ b/utils/docs/ci_status/report.py
@@ -1461,9 +1461,17 @@ def stale_after_hours(config, check):
 
 def apply_staleness(result, config, check):
     threshold = stale_after_hours(config, check)
-    distance_count, distance_ref = None, None
-    if result["status"] != IN_PROGRESS:
-        distance_count, distance_ref = result_revision_distance(config, result)
+    distance_count, distance_ref = result_revision_distance(config, result)
+    if result["status"] == IN_PROGRESS and distance_count and distance_count > 0:
+        stale = dict(result)
+        stale["revision_commits_behind"] = distance_count
+        stale["revision_compare_ref"] = distance_ref
+        stale["revision_distance"] = revision_distance_text(distance_count, distance_ref)
+        stale["stale_base_status"] = result["status"]
+        stale["status"] = UNKNOWN
+        stale["status_label"] = "Stale running"
+        stale["message"] = f"{result.get('message', 'CI run')} ({stale['revision_distance']})"
+        return stale
     if result["status"] != IN_PROGRESS and distance_count and distance_count > 0:
         stale = dict(result)
         stale["revision_commits_behind"] = distance_count
diff --git a/utils/docs/tests/test_ci_status.py b/utils/docs/tests/test_ci_status.py
index 0c4cb87a2..345d8d4af 100644
--- a/utils/docs/tests/test_ci_status.py
+++ b/utils/docs/tests/test_ci_status.py
@@ -366,6 +366,57 @@ class CIStatusTest(unittest.TestCase):
         self.assertEqual(CI_STATUS.STALE_PASSED, recent_passed["status"])
         self.assertIn("3 commits behind stable-synthetic", recent_passed["message"])
 
+    def test_apply_staleness_marks_running_result_from_old_revision_unknown(self):
+        config = {
+            "stale_after_hours": 168,
+            "branches": [{"name": "master", "label": "master"}],
+        }
+        stale_check = {"name": "Synthetic CI"}
+        running = {
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "revision": "1" * 40,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "build 8178; running: Winnie",
+        }
+
+        with mock.patch.object(CI_STATUS, "result_revision_distance", return_value=(467, "master")):
+            stale = CI_STATUS.apply_staleness(running, config, stale_check)
+
+        self.assertEqual(CI_STATUS.UNKNOWN, stale["status"])
+        self.assertEqual(CI_STATUS.IN_PROGRESS, stale["stale_base_status"])
+        self.assertEqual("Stale running", stale["status_label"])
+        self.assertEqual(467, stale["revision_commits_behind"])
+        self.assertEqual("467 commits behind master", stale["revision_distance"])
+        self.assertIn("467 commits behind master", stale["message"])
+
+    def test_apply_staleness_keeps_running_result_at_branch_head(self):
+        config = {
+            "stale_after_hours": 168,
+            "branches": [{"name": "master", "label": "master"}],
+        }
+        stale_check = {"name": "Synthetic CI"}
+        running = {
+            "branch": "master",
+            "branch_label": "master",
+            "check": "Synthetic CI",
+            "provider": "synthetic",
+            "required": True,
+            "revision": "2" * 40,
+            "status": CI_STATUS.IN_PROGRESS,
+            "message": "build 8185; running: Winnie",
+        }
+
+        with mock.patch.object(CI_STATUS, "result_revision_distance", return_value=(0, "master")):
+            current = CI_STATUS.apply_staleness(running, config, stale_check)
+
+        self.assertEqual(CI_STATUS.IN_PROGRESS, current["status"])
+        self.assertNotIn("stale_base_status", current)
+        self.assertNotIn("revision_distance", current)
+
     def test_github_actions_requests_history_for_the_named_workflow(self):
         check_config = {
             "name": "GitHub Actions / Linux",

-----------------------------------------------------------------------

Summary of changes:
 utils/docs/ci_status/report.py     |  65 ++++++---
 utils/docs/tests/test_ci_status.py | 275 +++++++++++++++++++++++++++++++++++++
 2 files changed, 323 insertions(+), 17 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list