A real 1h57m workout on 2026-08-24 synced to Firebase and the locker still locked the screen. The trigger was auth: screen_locker was the only app with no cached firebase_auth.json, so it fell back to a stale shared password and got HTTP 400 every run. The defect was what it did next — an unreadable primary source was reported as "no workout" rather than "could not check", which is the same conflation that took 2026-06-12. Recover the session by borrowing a live refresh token from a sibling app, track degraded sources as state instead of a log line, and say so on the lock screen: name the backend, the error, and how stale the data is, so a sync fault reads as a sync fault. Also fix two bugs found while finishing the recovery: restore_and_bonus.py banked `existing + bonus` with no idempotence guard while finish_2026_08_24.sh advertised "safe to re-run" — a second run silently granted the compensation hours again. Guard on a durable `shutdown_bonus_granted_for` list; the bonus map alone cannot answer the question, since a week's total mixes earned streak hours with corrections. _extra_benefits._save_state rebuilt state from four hardcoded keys, so the next weekly rollover would have dropped that guard and re-armed the double-grant. Merge onto loaded state instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EJmPLyT8NU4DzDJPV1KEX5
1.9 KiB
do NOT run tests unless specifically instructed to do so or before committing If tests fail on the same issue twice in a row, STOP and ask the user how to proceed instead of continuing to fix and retry. ALWAYS confirm that the feature you add / bug you fixed behaves as it should by running the program after your changes (not tests!) and inspecting output comparing it with what user wanted, after confirming by yourself ask user if the program behaves as they intended After running tests fix all coverage gaps and issues, do not ignore unless specifically instructed to do so You are NOT done until you install the new version on the phone itself (flutter install --debug from the workout_app directory, then adb shell monkey -p com.kuhy.workout_app to launch).
NEVER fail silently
A failure that says nothing is a bug. The PC's workout sync did nothing for
weeks because a missing token returned [] and real errors only logged at
INFO — nobody could tell "nothing to do" from "it broke".
- Swallowed exceptions must say why, loudly. Every
exceptmust either re-raiseor log atwarning/error/exception/criticalwith the concrete reason.debug/infodo NOT count — an invisible log is a silent failure with extra steps. Enforced byscripts/check_silent_failures.py(pre-commit hookno-silent-failures); ruff'sS110only catchesexcept: pass, which is the rare case. - Guard clauses that abort work must log too.
if token is None: returnis a silent no-op. Say what was missing and what it means. - Return something meaningful. Prefer a typed result (e.g.
_manual_push.PushResult(pushed, record_count, reason)) over a bareNone/[]/False, so the caller can react instead of guessing.reasonshould read like a sentence a human can act on. - No escape hatch —
# noqais banned repo-wide. If a failure really is benign, log it atwarningand move on.