env-doctor Telemetry Spec
Status: Draft v1
Purpose: Define opt-in telemetry that builds the compatibility graph — env-doctor's real moat — without burning trust in the ML community.
1. Goals and non-goals
Goals
- Capture enough signal from real environments to answer questions like "what stacks fail most often" and "which torch + CUDA combos coexist in the wild" — the data backbone of the compatibility intelligence network.
- Be defensibly privacy-respecting so the ML community keeps starring the repo instead of forking it to rip out telemetry.
- Be cheap to run and easy to query. SQLite or Postgres, append-only, no PII to manage.
- Build something an enterprise procurement team can read in 5 minutes and approve.
Non-goals
- Not a product analytics tool. Don't measure feature engagement, screen flow, or marketing funnels. That's a separate system if/when it's needed.
- Not crash reporting. Sentry exists.
- Not user identification. There is no user. There is an install.
2. Privacy principles (these are load-bearing — don't compromise)
- Opt-in, not opt-out. First run prints a notice and asks. Default is no. Never auto-enroll. If someone upgrades from a pre-telemetry version, treat them as new and ask again.
- No PII, ever. No usernames, hostnames, IP addresses stored, file paths, repo names, working directories, env var contents, package install paths, model names from local files, or anything a user typed.
- Anonymous install ID only. A UUIDv4 generated locally on first opt-in, stored in
~/.env-doctor/install_id. Never derived from any system property. Rotatable by the user (env-doctor telemetry reset-id).
- Coarsen everything. Round versions to minor where possible. Bucket VRAM to known SKU sizes. Drop patch-level OS kernel info beyond major.minor.
- Show the user what's sent.
env-doctor telemetry preview prints the exact JSON that would be sent for the current environment, with no transmission.
- One-line off switch.
env-doctor telemetry disable or ENV_DOCTOR_TELEMETRY=0. Honor it before any other config.
- CI is off by default. Even if the user opted in on their laptop, if the run is detected as CI, don't send. CI environments are often customer infrastructure and the opt-in didn't come from their org.
- Transparent ingestion. Publish a public dashboard (eventually) showing aggregated stats — proves you can't tie data to individuals because the public view IS the data.
3. What gets collected
Three event types. Nothing else.
3.1 check.completed
Fires when env-doctor check finishes. The core event. This is what builds the graph.
{
"event": "check.completed",
"schema_version": 1,
"timestamp": "2026-05-26T12:00:00Z",
"install_id": "550e8400-e29b-41d4-a716-446655440000",
"env_doctor_version": "0.3.3",
"platform": {
"os": "linux",
"distro": "ubuntu",
"distro_major": "22",
"kernel_major_minor": "6.5",
"arch": "x86_64",
"is_wsl": false,
"is_container": false,
"container_runtime": null,
"is_ci": false,
"ci_provider": null
},
"hardware": {
"gpus": [
{
"vendor": "nvidia",
"model_normalized": "rtx_4090",
"compute_capability": "8.9",
"vram_bucket": "24gb"
}
],
"gpu_count": 1,
"driver_version": "535.146.02",
"driver_max_cuda": "12.2"
},
"stack": {
"python_version": "3.11",
"package_manager": "uv",
"cuda_toolkit": "12.1",
"cudnn": "8.9",
"frameworks": {
"torch": { "version": "2.1.0", "cuda_variant": "cu121" },
"tensorflow": null,
"jax": null
},
"compiled_extensions": [
{ "name": "flash-attn", "version": "2.5.0" },
{ "name": "xformers", "version": "0.0.23" },
{ "name": "bitsandbytes", "version": "0.42.0" }
]
},
"result": {
"status": "pass",
"checks_passed": 12,
"checks_failed": 0,
"checks_warned": 1,
"failures": []
}
}
When a check fails, failures carries the structured fingerprint:
"failures": [
{
"check_id": "torch_cuda_runtime_match",
"severity": "error",
"expected_canonical": "cuda_runtime:12.1",
"actual_canonical": "cuda_runtime:12.5",
"error_fingerprint": "a3f8b2c1"
}
]
error_fingerprint is a short hash of the canonical (sanitized) error message — never the raw traceback. Same fingerprint across users means same bug. This is what lets you count "how many people hit this exact failure mode this week."
3.2 remediation.outcome
Fires after a user runs a suggested fix command. This is the highest-value event for the graph — it tells you which fixes actually work.
{
"event": "remediation.outcome",
"schema_version": 1,
"timestamp": "2026-05-26T12:05:00Z",
"install_id": "550e8400-e29b-41d4-a716-446655440000",
"env_doctor_version": "0.3.3",
"remediation_id": "reinstall_torch_cu121",
"for_failure": "torch_cuda_runtime_match",
"before_fingerprint": "a3f8b2c1",
"after_status": "pass",
"after_fingerprint": null,
"elapsed_seconds": 84
}
This is the dataset that turns env-doctor from "diagnoser" into "expert system." After 10K of these you can rank remediations by success rate per environment shape.
3.3 install.first_run
Fires once, when the user first opts in. Identifies the universe of environments env-doctor is deployed to — even before they run a check.
{
"event": "install.first_run",
"schema_version": 1,
"timestamp": "2026-05-26T12:00:00Z",
"install_id": "550e8400-e29b-41d4-a716-446655440000",
"env_doctor_version": "0.3.3",
"install_method": "uvx",
"platform": { "...same as above..." }
}
That's it. Three events. Resist adding a fourth.
4. What is explicitly NOT collected
Put this list verbatim in the privacy doc and in env-doctor telemetry --help:
- No usernames, hostnames, MAC addresses, or machine serial numbers
- No IP addresses (server drops the source IP before persistence; only retains coarse country from edge if needed, see §6)
- No file paths, working directories, virtualenv paths, conda env names
- No repo names, git remotes, or commit hashes
- No environment variable names or values
- No project names, model names from local files, or dataset names
- No package names beyond the curated allow-list in §5
- No raw error messages or tracebacks — only canonical fingerprints
- No timing-attack-able high-precision timestamps (round to nearest second)
- No screen size, terminal type, locale, or timezone string (UTC only)
5. The curated package allow-list
Don't send pip freeze. That leaks too much. Only report versions for a fixed list of packages that materially affect GPU compatibility:
Frameworks
torch, torchvision, torchaudio, tensorflow, tensorflow-gpu, jax, jaxlib
Compile-against-CUDA extensions
flash-attn, xformers, bitsandbytes, apex, auto-gptq, autoawq, vllm, deepspeed, triton, mamba-ssm, causal-conv1d, tiny-cuda-nn, nvdiffrast, pytorch3d, sageattention, nunchaku
Runtime/serving
onnxruntime-gpu, tensorrt, cupy, numba
Quantization / optimization
optimum, accelerate, peft
Maintain this list in env_doctor/telemetry/packages.py. Add packages only when a user reports a CUDA-related failure tied to one not on the list. Removing is fine too.
Anything not on this list is dropped before serialization. Document this in the privacy notice.
6. The pipeline
Client side
- Buffer events to
~/.env-doctor/telemetry-queue.jsonl (append-only).
- Flush asynchronously in a background thread, non-blocking. The CLI never waits on the network.
- Batch up to 10 events or 5 seconds, whichever comes first.
- POST to
https://telemetry.env-doctor.dev/v1/events with Content-Type: application/json.
- Retry: 3 attempts with exponential backoff (1s, 4s, 15s). After that, keep the events in the queue file for next run. Queue caps at 1 MB; oldest dropped beyond that.
- Timeout: 3 seconds. Telemetry never blocks the CLI for more than that.
- If
ENV_DOCTOR_TELEMETRY=0 or the user is opted out: queue file is never written.
Server side
FastAPI endpoint, same backend as the dashboard. SQLAlchemy + SQLite for now, Postgres later (per existing architecture plan).
- Endpoint:
POST /v1/events
- Request body:
{"events": [<event>, ...]}
- Server drops the client's source IP at the edge proxy. Do not log it. Do not derive country from it for now — defer that to v2 if you genuinely need geo.
- Validate against the JSON schema. Reject invalid payloads with 400 (the client will not retry on 4xx).
- Insert into
telemetry_events table.
- Return 202 Accepted. No response body needed.
Storage schema (SQLAlchemy sketch)
class TelemetryEvent(Base):
__tablename__ = "telemetry_events"
id = Column(Integer, primary_key=True)
received_at = Column(DateTime, default=datetime.utcnow, index=True)
event_type = Column(String, index=True) # check.completed, etc.
schema_version = Column(Integer)
install_id = Column(String(36), index=True)
env_doctor_version = Column(String, index=True)
payload = Column(JSON) # full event body
# Denormalized columns for fast graph queries — populated on insert
gpu_model = Column(String, index=True)
compute_capability = Column(String, index=True)
driver_version = Column(String, index=True)
cuda_toolkit = Column(String, index=True)
torch_version = Column(String, index=True)
torch_cuda_variant = Column(String, index=True)
python_version = Column(String, index=True)
os_distro = Column(String, index=True)
status = Column(String, index=True) # pass, fail, warning
Denormalize on insert. The JSON blob keeps full fidelity; the columns make the graph queries fast.
Retention
- Raw events: 18 months. After that, drop into a monthly aggregates table.
- Aggregates: indefinite. They're not personal data.
install.first_run events for an install that hasn't sent anything in 90 days: drop. Treat as churn.
7. Opt-in UX
First run
$ env-doctor check
env-doctor would like to send anonymous environment data to help build
a public compatibility database for the ML community.
What we send:
• GPU model, driver, CUDA, OS, Python version
• Versions of ML-related packages (torch, flash-attn, etc.)
• Whether each check passed or failed (no error text)
What we never send:
• Usernames, paths, hostnames, IPs, project names
• Anything you typed or any package outside the public allow-list
Full spec: https://env-doctor.dev/privacy
Preview what would be sent: env-doctor telemetry preview
Enable anonymous telemetry? [y/N]:
Default is N. Honor whatever they pick. Don't ask again unless they run env-doctor telemetry reset.
CLI commands
env-doctor telemetry status # show: on/off, install_id, events queued
env-doctor telemetry enable
env-doctor telemetry disable
env-doctor telemetry preview # print the JSON for current env, no send
env-doctor telemetry reset-id # rotate install_id
env-doctor telemetry purge # delete local queue and config
Config file
~/.env-doctor/config.toml
[telemetry]
enabled = false
install_id = "550e8400-e29b-41d4-a716-446655440000"
opted_in_at = "2026-05-26T12:00:00Z"
endpoint = "https://telemetry.env-doctor.dev/v1/events"
Environment variables (always win over config)
ENV_DOCTOR_TELEMETRY=0 — disabled
ENV_DOCTOR_TELEMETRY=1 — enabled (only if config also shows opted_in_at; never auto-opt-in via env var)
ENV_DOCTOR_TELEMETRY_ENDPOINT=... — override (useful for self-hosted later)
CI detection (auto-disable)
Skip telemetry when any of these are set: CI, GITHUB_ACTIONS, GITLAB_CI, CIRCLECI, JENKINS_URL, BUILDKITE, TF_BUILD (Azure Pipelines). Document this. Companies running env-doctor in CI shouldn't have to think about it.
8. Sample queries the graph unlocks
These are the questions you couldn't answer before. Write them down now so the schema is shaped to answer them.
"What torch + CUDA combos are people actually running?"
SELECT torch_version, torch_cuda_variant, cuda_toolkit, COUNT(DISTINCT install_id) AS users
FROM telemetry_events
WHERE event_type = 'check.completed'
AND received_at > NOW() - INTERVAL '30 days'
GROUP BY 1, 2, 3
ORDER BY users DESC;
"Which environment shape has the highest failure rate this week?"
SELECT gpu_model, cuda_toolkit, torch_version,
SUM(CASE WHEN status = 'fail' THEN 1 ELSE 0 END)::float / COUNT(*) AS fail_rate,
COUNT(*) AS sample_size
FROM telemetry_events
WHERE event_type = 'check.completed'
AND received_at > NOW() - INTERVAL '7 days'
GROUP BY 1, 2, 3
HAVING COUNT(*) > 20
ORDER BY fail_rate DESC;
"Which remediations actually work?"
SELECT remediation_id,
SUM(CASE WHEN after_status = 'pass' THEN 1 ELSE 0 END)::float / COUNT(*) AS success_rate,
COUNT(*) AS attempts
FROM telemetry_events
WHERE event_type = 'remediation.outcome'
GROUP BY 1
ORDER BY attempts DESC;
"Which compiled extension is breaking right now?"
Filter failures JSON for failures tied to flash-attn, xformers, etc., group by extension version + cuda_toolkit, count distinct install_ids. This is the alert that fires when a new release ships and starts breaking people.
The "Which remediations actually work" query is the one that turns env-doctor's suggestions from heuristics into evidence over time. Treat it as your north-star dataset.
9. The public privacy notice
Host at env-doctor.dev/privacy. Link from --help, the README, and the first-run prompt. Keep it short:
env-doctor optionally sends anonymous environment data to help build a public compatibility database. Telemetry is off by default and only enabled if you explicitly opt in.
When enabled, env-doctor sends:
- Your GPU model, driver version, CUDA toolkit, OS, Python version
- Versions of a fixed [list of ML-related packages](link) (torch, flash-attn, etc.)
- Whether each diagnostic check passed, failed, or warned
- A short fingerprint (hash) of failure types — never the error message itself
- An anonymous install ID, generated locally, that you can rotate or delete at any time
env-doctor never sends:
- Your username, hostname, IP address, or file paths
- Project names, repo names, git remotes, or commit hashes
- Any environment variables or anything you typed
- Any package outside the public allow-list
- Raw error messages or tracebacks
Telemetry is disabled automatically when env-doctor detects a CI environment.
Manage telemetry: env-doctor telemetry status / enable / disable / preview / purge
Mirror this verbatim in the README.
10. Implementation checklist
In rough order:
11. Open questions to resolve before shipping
- Where does the endpoint live? Same FastAPI as the dashboard or separate service? Separate is cleaner for scaling but more ops. Default: same service, separate router, until volume justifies splitting.
- Public dashboard now or later? A public "what env-doctor is seeing this week" page is a great trust signal and free marketing. Probably worth doing within 30 days of the first 500 events landing.
- Self-hosted telemetry endpoint? Enterprises will eventually want to keep telemetry internal but still get the local diagnostic value.
ENV_DOCTOR_TELEMETRY_ENDPOINT already covers this technically; document it explicitly in v2.
- What about Apple Silicon / Metal / ROCm? Schema needs a
vendor field on GPU (already there) but the rest needs review for non-NVIDIA. Punt to v2 unless an early user asks.
- Should the install_id rotate automatically? Some tools rotate every 30 days to make linking even harder. Trade-off: harder to compute "active installs." Recommendation: no auto-rotation in v1, user can rotate manually.
12. Trust-burning footguns to avoid
Listing these because every one of them has burned an OSS project before:
- ❌ Shipping telemetry without an obvious off switch (Audacity 2021)
- ❌ Defaulting to opt-in instead of opt-out (Homebrew, briefly)
- ❌ Sending the raw
pip freeze (leaks proprietary internal package names)
- ❌ Sending file paths "for debugging" (leaks usernames and project names)
- ❌ Bundling telemetry into a "minor" version bump without changelog disclosure
- ❌ Making the privacy doc longer than the README (people don't read it)
- ❌ Adding a new event type without bumping
schema_version and updating the privacy page
If anyone on the project ever asks "can we just collect this one extra thing?" — the answer is "open an issue, document why, update the privacy notice publicly, ship it in the next minor version with a CHANGELOG line." No exceptions.
env-doctor Telemetry Spec
Status: Draft v1
Purpose: Define opt-in telemetry that builds the compatibility graph — env-doctor's real moat — without burning trust in the ML community.
1. Goals and non-goals
Goals
Non-goals
2. Privacy principles (these are load-bearing — don't compromise)
~/.env-doctor/install_id. Never derived from any system property. Rotatable by the user (env-doctor telemetry reset-id).env-doctor telemetry previewprints the exact JSON that would be sent for the current environment, with no transmission.env-doctor telemetry disableorENV_DOCTOR_TELEMETRY=0. Honor it before any other config.3. What gets collected
Three event types. Nothing else.
3.1
check.completedFires when
env-doctor checkfinishes. The core event. This is what builds the graph.{ "event": "check.completed", "schema_version": 1, "timestamp": "2026-05-26T12:00:00Z", "install_id": "550e8400-e29b-41d4-a716-446655440000", "env_doctor_version": "0.3.3", "platform": { "os": "linux", "distro": "ubuntu", "distro_major": "22", "kernel_major_minor": "6.5", "arch": "x86_64", "is_wsl": false, "is_container": false, "container_runtime": null, "is_ci": false, "ci_provider": null }, "hardware": { "gpus": [ { "vendor": "nvidia", "model_normalized": "rtx_4090", "compute_capability": "8.9", "vram_bucket": "24gb" } ], "gpu_count": 1, "driver_version": "535.146.02", "driver_max_cuda": "12.2" }, "stack": { "python_version": "3.11", "package_manager": "uv", "cuda_toolkit": "12.1", "cudnn": "8.9", "frameworks": { "torch": { "version": "2.1.0", "cuda_variant": "cu121" }, "tensorflow": null, "jax": null }, "compiled_extensions": [ { "name": "flash-attn", "version": "2.5.0" }, { "name": "xformers", "version": "0.0.23" }, { "name": "bitsandbytes", "version": "0.42.0" } ] }, "result": { "status": "pass", "checks_passed": 12, "checks_failed": 0, "checks_warned": 1, "failures": [] } }When a check fails,
failurescarries the structured fingerprint:error_fingerprintis a short hash of the canonical (sanitized) error message — never the raw traceback. Same fingerprint across users means same bug. This is what lets you count "how many people hit this exact failure mode this week."3.2
remediation.outcomeFires after a user runs a suggested fix command. This is the highest-value event for the graph — it tells you which fixes actually work.
{ "event": "remediation.outcome", "schema_version": 1, "timestamp": "2026-05-26T12:05:00Z", "install_id": "550e8400-e29b-41d4-a716-446655440000", "env_doctor_version": "0.3.3", "remediation_id": "reinstall_torch_cu121", "for_failure": "torch_cuda_runtime_match", "before_fingerprint": "a3f8b2c1", "after_status": "pass", "after_fingerprint": null, "elapsed_seconds": 84 }This is the dataset that turns env-doctor from "diagnoser" into "expert system." After 10K of these you can rank remediations by success rate per environment shape.
3.3
install.first_runFires once, when the user first opts in. Identifies the universe of environments env-doctor is deployed to — even before they run a check.
{ "event": "install.first_run", "schema_version": 1, "timestamp": "2026-05-26T12:00:00Z", "install_id": "550e8400-e29b-41d4-a716-446655440000", "env_doctor_version": "0.3.3", "install_method": "uvx", "platform": { "...same as above..." } }That's it. Three events. Resist adding a fourth.
4. What is explicitly NOT collected
Put this list verbatim in the privacy doc and in
env-doctor telemetry --help:5. The curated package allow-list
Don't send
pip freeze. That leaks too much. Only report versions for a fixed list of packages that materially affect GPU compatibility:Frameworks
torch,torchvision,torchaudio,tensorflow,tensorflow-gpu,jax,jaxlibCompile-against-CUDA extensions
flash-attn,xformers,bitsandbytes,apex,auto-gptq,autoawq,vllm,deepspeed,triton,mamba-ssm,causal-conv1d,tiny-cuda-nn,nvdiffrast,pytorch3d,sageattention,nunchakuRuntime/serving
onnxruntime-gpu,tensorrt,cupy,numbaQuantization / optimization
optimum,accelerate,peftMaintain this list in
env_doctor/telemetry/packages.py. Add packages only when a user reports a CUDA-related failure tied to one not on the list. Removing is fine too.Anything not on this list is dropped before serialization. Document this in the privacy notice.
6. The pipeline
Client side
~/.env-doctor/telemetry-queue.jsonl(append-only).https://telemetry.env-doctor.dev/v1/eventswithContent-Type: application/json.ENV_DOCTOR_TELEMETRY=0or the user is opted out: queue file is never written.Server side
FastAPI endpoint, same backend as the dashboard. SQLAlchemy + SQLite for now, Postgres later (per existing architecture plan).
POST /v1/events{"events": [<event>, ...]}telemetry_eventstable.Storage schema (SQLAlchemy sketch)
Denormalize on insert. The JSON blob keeps full fidelity; the columns make the graph queries fast.
Retention
install.first_runevents for an install that hasn't sent anything in 90 days: drop. Treat as churn.7. Opt-in UX
First run
Default is N. Honor whatever they pick. Don't ask again unless they run
env-doctor telemetry reset.CLI commands
Config file
~/.env-doctor/config.tomlEnvironment variables (always win over config)
ENV_DOCTOR_TELEMETRY=0— disabledENV_DOCTOR_TELEMETRY=1— enabled (only if config also shows opted_in_at; never auto-opt-in via env var)ENV_DOCTOR_TELEMETRY_ENDPOINT=...— override (useful for self-hosted later)CI detection (auto-disable)
Skip telemetry when any of these are set:
CI,GITHUB_ACTIONS,GITLAB_CI,CIRCLECI,JENKINS_URL,BUILDKITE,TF_BUILD(Azure Pipelines). Document this. Companies running env-doctor in CI shouldn't have to think about it.8. Sample queries the graph unlocks
These are the questions you couldn't answer before. Write them down now so the schema is shaped to answer them.
"What torch + CUDA combos are people actually running?"
"Which environment shape has the highest failure rate this week?"
"Which remediations actually work?"
"Which compiled extension is breaking right now?"
Filter
failuresJSON for failures tied to flash-attn, xformers, etc., group by extension version + cuda_toolkit, count distinct install_ids. This is the alert that fires when a new release ships and starts breaking people.The "Which remediations actually work" query is the one that turns env-doctor's suggestions from heuristics into evidence over time. Treat it as your north-star dataset.
9. The public privacy notice
Host at
env-doctor.dev/privacy. Link from--help, the README, and the first-run prompt. Keep it short:Mirror this verbatim in the README.
10. Implementation checklist
In rough order:
~/.env-doctor/config.tomlreader/writerinstall_idgeneration andENV_DOCTOR_TELEMETRYenv var handlingcheck.completed,remediation.outcome,install.first_run)env-doctor telemetry ...CLI subcommands/v1/eventsendpointTelemetryEventmodel + Alembic migration--helpenv-doctor telemetry previewin every supported CI environment to confirm no PII slips in/v1/stats/publicendpoint11. Open questions to resolve before shipping
ENV_DOCTOR_TELEMETRY_ENDPOINTalready covers this technically; document it explicitly in v2.vendorfield on GPU (already there) but the rest needs review for non-NVIDIA. Punt to v2 unless an early user asks.12. Trust-burning footguns to avoid
Listing these because every one of them has burned an OSS project before:
pip freeze(leaks proprietary internal package names)schema_versionand updating the privacy pageIf anyone on the project ever asks "can we just collect this one extra thing?" — the answer is "open an issue, document why, update the privacy notice publicly, ship it in the next minor version with a CHANGELOG line." No exceptions.