Finish the three sync bugs: gate, SIGKILL flag, and silent catches
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
release-apk / release (APK) (push) Waiting to run

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177xwzcpfpWG2qca8Mr39q9
This commit is contained in:
Krzysztof Rudnicki 2026-08-09 22:28:18 +02:00
parent d388f90ed4
commit 688bd7b1de
3 changed files with 39 additions and 9 deletions

View File

@ -103,7 +103,6 @@ Future<bool> _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',

View File

@ -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<bool> 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();
}

View File

@ -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<FirebaseAccount?> 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;
}
}