Anonymous usage stats, opt-out with CASE_TELEMETRY=0 - #11
Conversation
cased sends three events to PostHog: install_ping at boot, install_heartbeat once per UTC day from the sweeper, and run_completed from the scheduler. Each carries a random install id (persisted in $CASE_HOME/telemetry.json), counts of computers, credentials, schedules and recent runs, and for run_completed the status, kind, duration and whether a screenshot was captured. Never a name, prompt, domain, credential, hostname or path. Off with CASE_TELEMETRY=0 or DO_NOT_TRACK=1, and off automatically inside test runs. Sends are daemon threads with a 5 s timeout and never raise.
| Case sends a few anonymous numbers that help us see what breaks: a random install | ||
| id, how many computers and schedules exist, and whether scheduled runs succeed. | ||
| Never a name, prompt, domain or credential. Turn it off with `CASE_TELEMETRY=0` | ||
| (or `DO_NOT_TRACK=1`) in `.env`. |
There was a problem hiding this comment.
The disclosure calls these statistics anonymous, but each direct POST reveals the installation's source IP to the operator-controlled ingest proxy, where it is associated with a stable install ID. Please describe the identifier as pseudonymous and disclose the transport metadata so operators can make an informed opt-out decision.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Reworded: the README now says the request reaches our server with the IP, and drops the word anonymous.
There was a problem hiding this comment.
Reworded: the README now says the request reaches our server with the IP, and drops the word anonymous.
| tmp = PATH + ".tmp" | ||
| with open(tmp, "w") as f: | ||
| json.dump(s, f, indent=2) | ||
| os.replace(tmp, PATH) |
There was a problem hiding this comment.
_save uses one shared .tmp path without consistently locking _load and _save. If the documented telemetry file is deleted while the service is running, concurrent completion events can both initialize it, race on that temporary file, and send different install IDs or drop an event when one os.replace loses the race. Serialize state initialization and updates, or use unique temporary files with an atomic locked update, to preserve the promised stable install identity.
There was a problem hiding this comment.
Fixed: _load takes the lock (RLock) so concurrent sends on a missing file mint a single id.
There was a problem hiding this comment.
Fixed: _load takes the lock (RLock) so concurrent sends on a missing file mint a single id.
Review follow-up: the README no longer calls the stats anonymous, since the ingest proxy sees the request IP like any web server. _load now holds the lock so two sends racing on a missing telemetry.json mint one id.
Adds a small `telemetry.py` to the control plane so we can see how many self-hosted installs exist and whether scheduled runs work.
What is sent, and only this: a random install id (kept in `$CASE_HOME/telemetry.json`), `self_host`, `install_age_days`, and counts of computers, credentials, schedules, enabled schedules and runs in the last 7 days. `run_completed` adds `status`, `kind`, `duration_s`, `had_artifact`. Never a name, prompt, domain, URL, username, hostname or credential. `tests/test_telemetry.py` enforces the allow-list, so a new property is a deliberate change here.
Events: `install_ping` at boot, `install_heartbeat` once per UTC day from the sweeper, `run_completed` from the scheduler.
Off: `CASE_TELEMETRY=0` or `DO_NOT_TRACK=1`. Off automatically inside test runs. Every send is a daemon thread with a 5 s timeout and never raises, so a missing network cannot slow a boot or a request.
Where: our PostHog ingest proxy at `bios.case.computer`, with the same public write-only token the website uses.
README gets one paragraph under Optional setup.
Tests: `.venv/bin/python tests/test_telemetry.py tests/test_lifecycle.py tests/test_scheduler.py`, all pass.
🤖 Generated with Claude Code
The PR appears safe to merge, with both previous review findings fully addressed and no new actionable issues identified.
Summary
CASE_TELEMETRY=0andDO_NOT_TRACK=1.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR B[Service boot] --> P[install_ping] W[Daily sweeper] --> H[install_heartbeat] S[Scheduled run completes] --> R[run_completed] P --> T[telemetry.capture] H --> T R --> T T --> D[Daemon send thread] D --> X[PostHog ingest proxy] T --> F[Locked telemetry.json state]Reviews (2) · Last reviewed commit: "Say the proxy sees the IP, and lock inst..."