The nightly trimClientLogs cron fetched one page of 500 records older than
14 days and deleted those, while about 1275 rows arrive a day. Measured on
production (read-only) on 2026-09-13: 30213 rows, 14420 of them past the
cutoff, the oldest from 2026-08-19, so the backlog grew by roughly 775 rows
a day and the documented 14-day retention never held.
The job now deletes in batches of 2000 with plain SQL until a batch comes
back short, so one run clears everything past the cutoff. Plain SQL instead
of $app.delete() per record is safe here: client_logs has no file fields, no
collection has a relation pointing at it and no hook watches its deletes, so
the record path would only add a transaction and a cascade lookup per row.
Batches keep each write lock short. The run logs "trimClientLogs" with the
deleted count and the cutoff to PocketBase's logs, and a failure is logged
instead of swallowed silently as before.
scripts/test_client_logs_trim.py starts a throwaway PocketBase 0.39.6 with
this repo's migrations and hooks, seeds stale and fresh rows, fires the job
through POST /api/crons/trimClientLogs and checks what is left. Against the
old hook it failed: 800 of 1300 stale rows left, no log line. With the fix:
0 of 1300 left, all 40 in-window rows kept (one just inside the cutoff), and
0 of 15000 stale rows left in 0.4 s at production scale.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Backgrounding started two flushes at once and both posted the pending rows:
17 of 438 sessions in 2026-08-30..09-13 have two session_summary rows, each
pair created within 0.23 s. Says how to count them (distinct device + ts +
event, or device + sessionId for a summary). Fixed in amber-app by running one
flush at a time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Documents the amber-app change that records a film whose TV is switched off
with it on screen. session_summary gains ended (closed | unseen), lastSeenAt
and playedOn; a session held on disk and never closed is written once at the
next launch, with its one health row; a new error row, session_hold_failed,
records a hold that could not be written, released or read.
Includes the measurement that ruled out a lifecycle hook: on the kids' box
ff3bcf, 13 of 13 films in 14 days left no summary, with no callback at their
end and 0 of 3572 api_usage rows delivered late.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The documented meta was the THIN row -- the leftover second writer that
amber-app just removed. It also never said that watchedS (elapsed watch
clock) and positionS (where playback reached) are different numbers, which
is exactly the distinction that made a never-started session readable.
Records that aggregates over rows before 2026-08-07 double-count Android
sessions, and gives the filter that excludes them.
The family runs Amber on hardware I can't debug directly, so the app batches
a low-volume diagnostic stream here: uncaught errors, player error codes,
per-session playback summaries, and the audio-delay a user dials in to fix
desync (the "a track falls behind" signal).
- Migration 1788000000_client_logs.js: owner-create-only, superuser-read-only
(list/view/update/delete = null); fields kind/event/message/meta/appVersion/
platform/device/ts + created.
- client_logs.pb.js: nightly cron trims rows older than 14 days.
- docs/telemetry-contract.md: event catalogue, how I query it, and the
privacy/redaction guarantees (host-only, never a token or full URL).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>