From 507030d63ba83f30b8d8955cf89a412a27afb57d Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Wed, 12 Aug 2026 19:17:29 +0200 Subject: [PATCH] Retry RunnerUp verification every 15 min, and stop hiding why it fails The auto-fill scan only ran once, at lock-screen login; a workout was uncreditable for the rest of the day if the phone wasn't adb-visible at that exact instant. Reuse the existing 15-minute workout-sync.timer to retry it. Also upgrade the RunnerUp verification failure paths from info to warning with the actual reason (no TCX export found, or root DB access unavailable on this non-rooted device) instead of a vague or invisible message. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_013SH7XBkpEqQX4KghHdgx9T --- screen_locker/_runnerup_db.py | 16 ++++++++++++++-- screen_locker/_runnerup_verification.py | 18 +++++++++++++++++- screen_locker/screen_lock.py | 7 +++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/screen_locker/_runnerup_db.py b/screen_locker/_runnerup_db.py index afc5b32..cb016d8 100644 --- a/screen_locker/_runnerup_db.py +++ b/screen_locker/_runnerup_db.py @@ -57,7 +57,14 @@ class RunnerUpDbMixin: root=True, ) if not ok: - _logger.info("Failed to copy RunnerUp DB to sdcard: %s", err) + _logger.warning( + "Root DB pull failed for %s (%s) — this device likely has no " + "root/su access, so this fallback path cannot work here. " + "RunnerUp workouts can only be verified via TCX export files; " + "enable RunnerUp's File Synchronizer auto-export on-device.", + pkg, + err, + ) shutil.rmtree(tmp_dir, ignore_errors=True) return None @@ -142,7 +149,12 @@ class RunnerUpDbMixin: """Verify today's run via root DB pull (fallback path).""" db_path = self._pull_runnerup_db() if db_path is None: - return "not_verified", "Could not retrieve RunnerUp database from phone" + return ( + "not_verified", + "No TCX export found and root DB access is unavailable on this " + "device — enable RunnerUp's File Synchronizer auto-export so " + "runs land in /sdcard/Documents/RunnerUp/", + ) try: run_data = self._query_todays_run(db_path) diff --git a/screen_locker/_runnerup_verification.py b/screen_locker/_runnerup_verification.py index b3e7f1b..aef33af 100644 --- a/screen_locker/_runnerup_verification.py +++ b/screen_locker/_runnerup_verification.py @@ -138,6 +138,14 @@ class RunnerUpVerificationMixin(RunnerUpDbMixin): today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d") exports = self._find_runnerup_exports_for_date(today) if not exports: + _logger.warning( + "No RunnerUp TCX export found for %s in %s — a run today will " + "not be verified unless RunnerUp's File Synchronizer " + "auto-export is enabled and the app stays enabled long enough " + "to sync (check it isn't disabled by focus mode).", + today, + RUNNERUP_EXPORT_DIRS, + ) return None # Try each file; return the best result (verified > validation error). @@ -174,6 +182,13 @@ class RunnerUpVerificationMixin(RunnerUpDbMixin): continue status, msg = self._validate_runnerup_data(data) if status != "verified": + _logger.warning( + "RunnerUp export %s for %s did not qualify (%s): %s", + remote, + date_str, + status, + msg, + ) continue workout_data = { "type": "runnerup_verified", @@ -285,5 +300,6 @@ class RunnerUpVerificationMixin(RunnerUpDbMixin): return file_result # Path 2: root DB pull (fallback when no export files found yet). - _logger.info("No TCX exports found today; trying root DB pull...") + # On a non-rooted device this path cannot succeed; _verify_runnerup_via_db + # reports that plainly rather than as a generic failure. return self._verify_runnerup_via_db() diff --git a/screen_locker/screen_lock.py b/screen_locker/screen_lock.py index 0dab4ae..e020f46 100755 --- a/screen_locker/screen_lock.py +++ b/screen_locker/screen_lock.py @@ -234,8 +234,15 @@ class ScreenLocker( ``workout-sync.timer`` unit calls. Sync used to happen only inside the locker's startup path, so a workout finished after login stayed invisible until the next login. + + Also re-runs the RunnerUp TCX backfill here, not just at login: the + login-time scan gets exactly one shot, and if the phone isn't + adb-visible at that instant (e.g. USB debugging not yet + authorized), a same-day run stays uncredited until the next login. + Repeating it every 15 minutes closes that gap. """ self._ingest_synced_manual_workouts() + self._auto_fill_week_runnerup_bonus() def _ingest_synced_manual_workouts(self) -> None: """Sync manual workouts: publish this PC's, ingest everyone else's.