feat(trayicon): detect database-locked state and show recovery notification - #130
TimeToBuildBob wants to merge 5 commits into
Conversation
…cation When aw-server's log contains 'database is locked' errors (e.g. caused by a third-party watcher growing the SQLite DB too large), aw-qt now: - Shows a system tray notification with the data directory path and a sqlite3 backup command so users can export their data without the web UI - Checks every 30 seconds; notifies once per locked-state-start (no spam) - Resets the notification gate when the lock clears Also adds "Open data folder" to the tray menu alongside log/config. Fixes a usability gap reported in ActivityWatch/activitywatch#1394 where a locked DB made the export page inaccessible in a circular failure.
Greptile SummaryThis PR detects database-lock messages in recent server logs, reports affected server modules through the manager, and provides recovery guidance through a tray notification.
Confidence Score: 4/5The PR is not yet safe to merge because a recovery followed by another lock can remain hidden while the earlier lock message is still in the detection window. The notification state is derived from lock-phrase presence within a rolling 200-line tail rather than an actual lock-event transition, so recovery does not clear suppression and a subsequent lock can fail to notify the user. Files Needing Attention: aw_qt/manager.py, aw_qt/trayicon.py Important Files Changed
Reviews (3): Last reviewed commit: "fix(trayicon): use single window for db-..." | Re-trigger Greptile |
After a server recovered from a SQLite lock, the 200-line historical window kept returning True for has_db_locked_error, leaving the module in _db_locked_notified. A subsequent new lock event was then silently suppressed because the module appeared still-notified. Fix: use a 20-line window when deciding to *clear* the notification state. A recently-recovered server will quickly produce clean log lines that push lock messages out of this shorter window, allowing a new lock event to fire a fresh notification. The broader 200-line window is retained for *detecting* lock events (sensitivity). Also adds recent_lines param to get_db_locked_modules() and tests for the tail-boundary and parameter-forwarding behaviour.
|
Fixed the lock-state transition bug Greptile identified (commit a3eed35). The bug: After a server recovered from a SQLite lock, the 200-line historical window kept returning The fix: Use a shorter 20-line window when clearing notification state ( Also added two tests:
|
…ines() Avoids reading the entire log file into memory on every 30-second poll. deque(f, maxlen=N) streams lines and keeps only the last N, so memory usage is O(N) regardless of log size. Co-Authored-By: Bob <bob@superuserlabs.org>
|
@greptileai review |
The two-window approach (200-line detect, 20-line clear) created a dead zone: when 20-199 unrelated lines accumulated after a lock error, the 20-line window cleared _db_locked_notified while the 200-line window still reported the module as locked, causing a new notification every 30-second poll indefinitely. Fix: use the already-computed locked_names (200-line window) for the set intersection instead of a separate 20-line query. A module stays suppressed until the lock error ages fully out of the 200-line window, then a subsequent new lock event triggers a fresh notification.
|
Fixed the P1 from round 2 (commit 6609560). The two-window approach introduced a repeat-notification loop: when 20–199 unrelated lines accumulated after a lock error, the 20-line window cleared Fix: single-window clearing using the same All three Greptile findings addressed:
CI re-running on new head. |
|
@greptileai review |
🤖 AI code reviewSafe to merge — no P0/P1 findings on latest reviewUpdated after inline dispositions on finding threads — this is the current state; the verdict below is frozen at review time and is kept as the historical record of that pass. Finding disposition
Adds database-locked detection to aw-qt: Module.has_db_locked_error() scans the tail of a module's log for SQLite lock phrases, Manager.get_db_locked_modules() filters to alive server modules, and TrayIcon polls every 30 seconds to show a one-time notification with data-dir and sqlite3 backup instructions. Also adds an 'Open data folder' tray menu item and unit tests for the new detection logic. Needs a look — P2 onlyConfidence 4/5 1 finding ·
|
| commit | score | findings | engine | when |
|---|---|---|---|---|
66095601f601 |
3/5 | 4 | llm | 2026-09-16 08:42 UTC |
Reviewed 0410b07b8057 · openrouter/deepseek/deepseek-v4-flash-0731 · llm engine · 75s · about this reviewer
Maintainer commands
@TimeToBuildBob review (own line) — fresh review · @TimeToBuildBob fix — a worker acts on the findings. Once per comment; 👀 = received.
A poll that raises (e.g. UnicodeDecodeError on an unexpectedly-encoded log line, which has_db_locked_error's OSError handler does not cover) previously skipped the singleShot reschedule and killed the detection loop permanently. Wrap each poll body in try/finally so the timer is always rescheduled. Git-Session-Id: f88056d1-92a9-5a09-9bc2-7d3362f1244c
Merge recommendation (convergence adjudication)Adjudicated against head Fixed:
Remaining (non-blocking, dismissed with reasons):
All four review threads are resolved. CI: green on Domain risk: notification behavior is platform-dependent (Qt system-tray Convergence: detector reports Ready for maintainer judgment — merge-ready does not mean auto-merge. |
Merge recommendation (convergence adjudication, follow-up)Adjudicated against head Fixed (prior session, still on this head):
Remaining (non-blocking, dismissed this session):
Earlier P2s on this head remain dismissed: display-only sqlite3 quoting; Verified against: CI: green on this head (ubuntu-22.04, macos-14, windows-latest). Domain risk: tray Convergence: Ready for maintainer judgment — merge-ready does not mean auto-merge. No further Greptile trigger from this session. |
|
Closing for now, might reopen if it recurs for people. |
Summary
Module.has_db_locked_error()to detect SQLite 'database is locked' in recent server logsManager.get_db_locked_modules()to surface which server modules are lockedsqlite3backup command when a locked state is detected (once per lock event, no spam)Motivation
Users hit a circular failure: a third-party watcher (e.g. aw-watcher-utilization) can grow the SQLite database until it locks, which then causes the web UI to 500-loop — making the built-in export page inaccessible. The tray notification gives an escape hatch when the only path to recovery is bypassing the UI entirely.
Reported in: ActivityWatch/activitywatch#1394
Test plan
tests/test_manager.pycover the log-parsing detection and server-name filtering