From 86be044116624bd473644f3a7422bd7d44422cc7 Mon Sep 17 00:00:00 2001 From: Deeman Date: Sat, 28 Feb 2026 22:17:41 +0100 Subject: [PATCH] fix(supervisor): stop infinite deploy loop in web_code_changed() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HEAD~1..HEAD always shows the same diff after os.execv reloads the process — every tick triggers deploy.sh if the last commit touched web/. Fix: track the last-seen HEAD in a module-level variable. On first call (fresh process after os.execv), fall back to HEAD~1 so the newly-deployed commit is evaluated once. Recording HEAD before returning means the same commit never fires twice, regardless of how many ticks pass. Also remove two unused imports (json, urllib.request) caught by ruff. Co-Authored-By: Claude Sonnet 4.6 --- src/padelnomics/supervisor.py | 40 ++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/padelnomics/supervisor.py b/src/padelnomics/supervisor.py index 080fa22..02ccfbe 100644 --- a/src/padelnomics/supervisor.py +++ b/src/padelnomics/supervisor.py @@ -17,14 +17,12 @@ Usage: """ import importlib -import json import logging import os import subprocess import sys import time import tomllib -import urllib.request from collections import defaultdict from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import UTC, datetime @@ -269,14 +267,46 @@ def run_export() -> None: send_alert(f"[export] {err}") +_last_seen_head: str | None = None + + def web_code_changed() -> bool: - """Check if web app code or secrets changed since last deploy (after git pull).""" + """True on the first tick after a commit that changed web app code or secrets. + + Compares the current HEAD to the HEAD from the previous tick. On first call + after process start (e.g. after os.execv reloads new code), falls back to + HEAD~1 so the just-deployed commit is evaluated exactly once. + + Records HEAD before returning so the same commit never triggers twice. + """ + global _last_seen_head result = subprocess.run( - ["git", "diff", "--name-only", "HEAD~1", "HEAD", "--", + ["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=10, + ) + if result.returncode != 0: + return False + current_head = result.stdout.strip() + + if _last_seen_head is None: + # Fresh process — use HEAD~1 as base (evaluates the newly deployed tag). + base_result = subprocess.run( + ["git", "rev-parse", "HEAD~1"], capture_output=True, text=True, timeout=10, + ) + base = base_result.stdout.strip() if base_result.returncode == 0 else current_head + else: + base = _last_seen_head + + _last_seen_head = current_head # advance now — won't fire again for this HEAD + + if base == current_head: + return False + + diff = subprocess.run( + ["git", "diff", "--name-only", base, current_head, "--", "web/", "Dockerfile", ".env.prod.sops"], capture_output=True, text=True, timeout=30, ) - return bool(result.stdout.strip()) + return bool(diff.stdout.strip()) def current_deployed_tag() -> str | None: