From a5f7b036a829b9642cdd40c967fede1e5845f0ce Mon Sep 17 00:00:00 2001 From: Kit OC5 Date: Wed, 23 Sep 2026 15:36:19 -0400 Subject: [PATCH] guards: skip a commit the sync hasn't fetched yet, quietly (race, not error) The bridge reads PR/default heads from GitHub after the sync's fetch; a push in between isn't in the clone until the next cycle. Both guards logged a CalledProcessError for it (windy-pro main 40 s after the fetch). Now None = nothing posted this cycle; the next one scans it. Co-Authored-By: Claude Opus 5.5 --- api/tests/test_compute_guard.py | 9 +++++++++ scripts/ci_hygiene.py | 2 +- scripts/compute_guard.py | 13 ++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/api/tests/test_compute_guard.py b/api/tests/test_compute_guard.py index 28b1eb8..2a28699 100644 --- a/api/tests/test_compute_guard.py +++ b/api/tests/test_compute_guard.py @@ -182,3 +182,12 @@ def test_code_with_a_trailing_comment_still_counts(): def test_windy_pro_desktop_is_byok_but_the_account_server_is_not(): assert cg.allowed("windy-pro", "src/client/desktop/main.js", ALLOW) assert not cg.allowed("windy-pro", "account-server/src/routes/translations.ts", ALLOW) + + +def test_a_commit_not_fetched_yet_is_skipped_not_an_error(tmp_path, monkeypatch): + bare, sha = _repo(tmp_path, {"app/llm.py": "import anthropic\n"}) + monkeypatch.setattr(cg, "WORK", tmp_path) + monkeypatch.setattr(cg, "CACHE", tmp_path / "cache.json") + (tmp_path / "windy-chat.git").symlink_to(bare) + assert cg.check("windy-chat", "f" * 40, "main", True) is None # pushed after the fetch + assert [f.kind for f in cg.check("windy-chat", sha, "main", True)] == ["provider SDK"] diff --git a/scripts/ci_hygiene.py b/scripts/ci_hygiene.py index 5572e31..aed40f3 100644 --- a/scripts/ci_hygiene.py +++ b/scripts/ci_hygiene.py @@ -145,7 +145,7 @@ def scan_line(path: str, text: str) -> list[tuple[str, str]]: def check(repo: str, sha: str, default_branch: str, is_default_head: bool): bare = cg.WORK / f"{repo}.git" - if not bare.is_dir(): + if not bare.is_dir() or not cg.fetched(bare, sha): # pushed after the fetch: next cycle return None allow = cg.load_allow(ALLOW_FILE) rules = hashlib.sha256((PREFILTER + INCLUDE.pattern + EXACT_PY.pattern + EXACT_NPM.pattern).encode()).hexdigest()[:8] diff --git a/scripts/compute_guard.py b/scripts/compute_guard.py index 0ebe03d..b5ef67a 100644 --- a/scripts/compute_guard.py +++ b/scripts/compute_guard.py @@ -225,10 +225,21 @@ def cached_scan(key: str, fn) -> list[Finding]: return result +def fetched(bare: Path, sha: str) -> bool: + """Is `sha` in the sync clone yet? The bridge learns PR / default heads from + GitHub's API AFTER the sync fetched, so a push in between is simply not here + until the next 5-min cycle. That is a race, not an error: skip quietly.""" + try: + _git(bare, "cat-file", "-e", f"{sha}^{{commit}}") + return True + except subprocess.CalledProcessError: + return False + + def check(repo: str, sha: str, default_branch: str, is_default_head: bool) -> list[Finding] | None: """Findings for one commit, or None when the guard can't run (never a fake OK).""" bare = WORK / f"{repo}.git" - if not bare.is_dir(): + if not bare.is_dir() or not fetched(bare, sha): return None allow = load_allow() fp = _fingerprint(allow)