Usage ping

September 26, 2026 · View on GitHub

中文

LoopX can send one anonymous, opt-in ping per day so maintainers can count active installations instead of estimating them. It is off by default: nothing is read, written, or sent until the machine owner runs loopx usage-ping enable, and LoopX never prompts for it.

loopx usage-ping            # show consent, whether sending is active, and the exact payload
loopx usage-ping enable     # opt in; creates a random installation id
loopx usage-ping disable    # opt out; forgets the installation id

Payload

The whole request body is this JSON object (loopx_usage_ping_v0). There are no other fields, and the collector rejects any body with extra fields.

FieldExampleMeaning
schemaloopx_usage_ping_v0Payload version.
install_id3f0c…-4…Random UUIDv4 created by enable. Not derived from the machine, user, or account.
version1.2.0LoopX package version.
osdarwinOne of darwin, linux, windows, other.
python3.12Python major.minor.
channelpipHow LoopX was installed: pip, local_release, source, or unknown.

LoopX does not read or send project names, goal or todo contents, paths, hostnames, usernames, account ids, command lines, environment variables, or error reports. loopx usage-ping prints the next payload verbatim so you can check it.

When it sends

  • Only when consent is enabled, a collector endpoint is configured, and no switch below blocks it.
  • At most once per UTC day, triggered by the first loopx command of the day. The request runs in a detached background process with a 3-second timeout, so it never slows down or fails the command. A failed request is not retried until the next day.
  • loopx usage-ping itself never triggers a send.

The default endpoint in this source version is https://loopx-usage-collector.huangrt01.workers.dev/v0/ping. LOOPX_USAGE_PING_ENDPOINT overrides it with another https:// collector (plain http:// is accepted only for loopback addresses, for local testing). Endpoint availability does not grant consent: machines remain off until enable. Older builds with an empty default can use this environment variable without waiting for a release. A network that cannot reach workers.dev will not send successfully; the command still completes normally.

Switches

Any of these blocks sending, whatever the stored consent:

VariableBlocks when
LOOPX_USAGE_PING0, false, no, or off
DO_NOT_TRACKset to anything other than empty or 0
CIset to anything other than empty, 0, or false

Local state

Consent lives in ~/.codex/loopx/usage-ping.json (loopx_usage_ping_state_v0, file mode 0600). It is machine-level, not per project. disable rewrites it without the id, so a later enable is a new, unlinkable installation. Deleting the file returns the machine to undecided.

Collector and published numbers

The collector is a small Cloudflare Worker with a D1 database whose source is in apps/usage-collector. It stores only the payload fields plus the UTC day. It does not store IP addresses, user agents, or request metadata, and deletes rows after 400 days.

Its public GET /v0/stats endpoint publishes:

  • monthly_active: distinct installation ids with at least one ping in a calendar month (UTC);
  • new_installs: ids first seen in that month;
  • rolling_30d_active and daily_active for the last 30 days;
  • a current-month breakdown by version, OS, and channel, where any bucket with fewer than 5 installations is merged into other.

These are lower bounds. They count only machines that opted in, and a machine that is disabled and re-enabled counts as a new installation.

The frontend Settings → Global capabilities includes an optional usage reminder with the enable, status and disable commands. It does not change consent; the machine owner chooses explicitly in the terminal. Repeating enable preserves the current identity and daily attempt. Disable remains final even when an old background request finishes; re-enable creates a new identity. An already-sent request cannot be recalled.