Skip to content

feat: anonymous usage telemetry (opt-out) - #101

Merged
doramirdor merged 1 commit into
mainfrom
feat/usage-telemetry
Oct 15, 2026
Merged

doramirdor merged 1 commit into
mainfrom
feat/usage-telemetry

Conversation

@doramirdor

Copy link
Copy Markdown
Collaborator

Summary

Adds anonymous usage telemetry so maintainers can see active installs, versions, platforms, and which features get used. It is on by default, prints a notice on first run, and can be turned off three ways:

nadirclaw telemetry off        # persists in ~/.nadirclaw/telemetry.json
export NADIRCLAW_TELEMETRY=0
export DO_NOT_TRACK=1

Events go to PostHog (us.i.posthog.com, the same project as getnadir.com), keyed by a random install id:

Event When Properties
nadirclaw_cli Each CLI command Command name only (serve, claude onboard, auth openai login), never arguments or options
nadirclaw_serve Server start Tier count, provider names, public model ids (Anthropic/OpenAI/Google only), classifier, optimize mode, auth/PII/prompt-guard toggles, credential source per provider
nadirclaw_usage Every 6 h and at shutdown, only if requests were served Deltas: requests per tier, errors, tokens, cost, cache hits, fallbacks, tokens saved (read from the existing Prometheus counters in metrics.py)

Every event also carries the version, Python version, OS, CPU architecture, and whether it is running in CI or Docker.

Never sent: prompts, responses, keys, file paths, hostnames, command arguments, or model names from self-hosted or custom providers (Ollama, Together, Replicate, fine-tunes). claude read-hook sends nothing, because it runs on every Claude Code file read.

Design

  • The whole feature is one file, nadirclaw/analytics.py, using only the standard library (urllib, threading). It is separate from nadirclaw/telemetry.py, which is OpenTelemetry tracing to the user's own collector.
  • Command names are taken by matching sys.argv against the commands click has registered. A token that is not a registered command name stops the walk, so arguments are never sent.
  • Each send runs on a daemon thread with a 3 s timeout. Short CLI commands wait at most 1 s for it at exit (measured 1.06 s against an unreachable address). Every entry point is wrapped so a telemetry error cannot break a command or the server.
  • The server's credential-source loop already resolves each provider's credential once. It now records those sources for the startup event instead of resolving them a second time, which avoids a second OAuth refresh.
  • tests/conftest.py sets NADIRCLAW_TELEMETRY=0, so the test suite never sends events.

Known limits

  • PostHog records the connecting IP address, and this project has anonymize_ips: false. The README says so.
  • If $HOME is not writable (some containers), each run creates a new install id, which inflates user counts. A ponytail: comment in the code names the fix: hash /etc/machine-id.

Test plan

  • tests/test_analytics.py (8 tests): only command names are sent, never arguments; the three opt-outs send nothing and create no id; telemetry off persists; usage reports are deltas and skipped when idle; private model names are filtered out; telemetry errors never propagate
  • Full suite with a clean HOME: 936 passed, 3 skipped
  • Manual: telemetry status/on/off, the env override message, and the exit delay with no network
  • Live: a test event (nadirclaw_telemetry_test) returned {"status":"Ok"} from the PostHog project the key belongs to

🤖 Generated with Claude Code

Report coarse usage to the maintainers' PostHog project under a random
install id so we can see active installs, versions, platforms and which
features get used:

- nadirclaw_cli: the CLI command name per invocation (never arguments)
- nadirclaw_serve: routing config at server start (tier count, provider
  names, public model ids, classifier, optimize mode, feature toggles,
  credential source)
- nadirclaw_usage: per-tier request, token, cost, cache and fallback
  deltas every 6h and at shutdown, only when requests were served

Prompts, responses, keys, paths, hostnames and self-hosted or fine-tuned
model names are never sent. `claude read-hook` sends nothing since it
runs on every Claude Code file read. A first-run notice explains it;
disable with `nadirclaw telemetry off`, NADIRCLAW_TELEMETRY=0 or
DO_NOT_TRACK=1. Sends run on a daemon thread with a 3s timeout, CLI exit
waits at most 1s, and every entry point swallows its own errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@doramirdor
doramirdor merged commit 5a39367 into main Oct 15, 2026
3 checks passed
doramirdor added a commit that referenced this pull request Oct 15, 2026
- Anonymous usage telemetry, on by default with a first-run notice:
  CLI command names, routing config at server start, and per-tier
  request/token/cost counts every 6h, keyed by a random install id.
  Opt out with `nadirclaw telemetry off`, NADIRCLAW_TELEMETRY=0 or
  DO_NOT_TRACK=1 (#101).

Bumps version to 0.25.0 and dates the changelog section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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