From 688bd7b1de62ec123c0a5b26b2a00754df880b82 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sun, 9 Aug 2026 22:28:18 +0200 Subject: [PATCH] Finish the three sync bugs: gate, SIGKILL flag, and silent catches Auditing the whole fleet for the bugs found on the desktop turned up three that were still live here. 1. `--virtual-time-budget=45000` was still in the provisioning args. The previous commit added the comment explaining why it must not be there but left the flag itself, so headless Chrome kept SIGKILLing itself (exit -9) and could still truncate a sync. Actually removed now; a provisioning run exits 0. 2. `backgroundSyncPush` gated on `settings.isConfigured` -- "has a GitHub token" -- so the WorkManager background push was skipped entirely on a device connected only to Firebase. Same defect as _autoSync's, one layer down. Either backend now counts. 3. Three swallowed exceptions, all in the sync path: the in-isolate config read, the background push itself, and `loadAccount()`. The last one is the worst -- it hid *why* provisioning failed, which is indistinguishable from "no account set" until it says so, and cost real debugging time this session. All log at warning/error with the stack trace and stay non-fatal, per the repo's no-silent-failures rule. Verified after the change, not just compiled: provisioning exits 0 and the desktop (c891b813) still holds 359 food_log records, identical to the PC daemon's, 0 missing. 522 tests, analyzer and pre-commit clean. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_0177xwzcpfpWG2qca8Mr39q9 --- app/bin/diet_guard_desktop.dart | 1 - app/lib/services/background_sync_service.dart | 33 ++++++++++++++++--- app/lib/services/firebase_backend.dart | 14 ++++++-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/app/bin/diet_guard_desktop.dart b/app/bin/diet_guard_desktop.dart index d4a2f61..90b71d8 100644 --- a/app/bin/diet_guard_desktop.dart +++ b/app/bin/diet_guard_desktop.dart @@ -103,7 +103,6 @@ Future _provisionSyncAccount(String home, int port) async { '--headless=new', '--disable-gpu', '--user-data-dir=${_profileDir(home)}', - '--virtual-time-budget=45000', // Surface the page's own log() output, so a failure inside the app // is visible in the terminal that started the provisioning run. '--enable-logging=stderr', diff --git a/app/lib/services/background_sync_service.dart b/app/lib/services/background_sync_service.dart index 10abd11..29b1dae 100644 --- a/app/lib/services/background_sync_service.dart +++ b/app/lib/services/background_sync_service.dart @@ -5,6 +5,7 @@ /// reconnect and uploads the log without the app being reopened. library; +import 'dart:developer'; import 'package:diet_guard_app/services/app_settings_service.dart'; import 'package:diet_guard_app/services/budget_history_service.dart'; import 'package:diet_guard_app/services/firebase_backend.dart'; @@ -41,16 +42,38 @@ Future backgroundSyncPush({http.Client? httpClient}) async { final SyncSettings settings; try { settings = await SyncSettings.load(); - } on Exception { - return false; // couldn't read config in-isolate; let WorkManager retry + } on Exception catch (error, stackTrace) { + log( + 'diet_guard background sync: cannot read config in-isolate', + level: 900, + error: error, + stackTrace: stackTrace, + ); + return false; // let WorkManager retry + } + // Either backend counts. `isConfigured` means "has a GitHub token", so + // gating on it alone silently skips every background push on a device + // connected only to Firebase. + if (!settings.isConfigured && await openFirebase() == null) { + log( + 'diet_guard background sync: no backend configured; nothing to push', + level: 900, + ); + return true; // nothing to push; don't retry } - if (!settings.isConfigured) return true; // nothing to push; don't retry final client = createGitHubClient(settings, httpClient: httpClient); try { await runSync(await syncBackend(client)); return true; - } on Exception { - return false; // offline / transient GitHub error -> retry with backoff + } on Exception catch (error, stackTrace) { + // Never silent: this is the only signal that a background push failed. + log( + 'diet_guard background sync failed', + level: 1000, + error: error, + stackTrace: stackTrace, + ); + return false; // offline / transient error -> retry with backoff } finally { client.close(); } diff --git a/app/lib/services/firebase_backend.dart b/app/lib/services/firebase_backend.dart index 8b86649..327b011 100644 --- a/app/lib/services/firebase_backend.dart +++ b/app/lib/services/firebase_backend.dart @@ -12,6 +12,7 @@ /// half. On Android there is no such file. library; +import 'dart:developer'; import 'package:crdt_sync/crdt_sync.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -59,9 +60,16 @@ Future loadAccount() async { final provisioned = await accountFromWrapper(Uri.base); if (provisioned != null) await saveAccount(provisioned); return provisioned; - } on Object { - // No secret service available: behave as "not configured" rather than - // crashing the settings screen. + } on Object catch (error, stackTrace) { + // Still "not configured" rather than crashing the settings screen -- but + // never silent: this hid *why* provisioning failed, which is + // indistinguishable from "no account set" until you say so. + log( + 'loadAccount failed; treating this device as not configured', + level: 1000, + error: error, + stackTrace: stackTrace, + ); return null; } }