Skip to content

Anonymous usage stats, opt-out with CASE_TELEMETRY=0 - #11

Merged
ishu86 merged 2 commits into
mainfrom
feat/install-stats
Sep 12, 2026
Merged

ishu86 merged 2 commits into
mainfrom
feat/install-stats

Conversation

@ishu86

@ishu86 ishu86 commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

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

RetriggerConfidence Score: 5/5

The PR appears safe to merge, with both previous review findings fully addressed and no new actionable issues identified.

Fix All in CursorFindings

  1. P2 Telemetry is pseudonymous
  2. P2 Telemetry state can race

Summary

  • Sends aggregate installation and scheduling metrics through a daemon thread.
  • Records scheduled-run outcomes without prompts, names, credentials, or other free-form user data.
  • Supports CASE_TELEMETRY=0 and DO_NOT_TRACK=1.
  • Documents telemetry collection and transport metadata.
  • The latest changes fully address the previous disclosure and state-race findings.

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]
Loading

Reviews (2) · Last reviewed commit: "Say the proxy sees the IP, and lock inst..."

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.
Comment thread README.md Outdated
Comment on lines +91 to +94
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`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Telemetry is pseudonymous

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!

Fix in Cursor Fix in Claude Code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded: the README now says the request reaches our server with the IP, and drops the word anonymous.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded: the README now says the request reaches our server with the IP, and drops the word anonymous.

Comment on lines +59 to +62
tmp = PATH + ".tmp"
with open(tmp, "w") as f:
json.dump(s, f, indent=2)
os.replace(tmp, PATH)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Telemetry state can race

_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.

Fix in Cursor Fix in Claude Code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: _load takes the lock (RLock) so concurrent sends on a missing file mint a single id.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@ishu86
ishu86 merged commit 523a0ea into main Sep 12, 2026
4 checks passed
@ishu86
ishu86 deleted the feat/install-stats branch September 12, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant