Telemetry
Pilum can share anonymous build telemetry so we can see which recipes, steps and versions fail more often, and catch a regression the week a release ships. On your machine it is off unless you say yes. The GitHub Action turns it on by default and says so in the job log. Telemetry arrived in Pilum v0.11.0.
Turning it on or off
The first time you run any Pilum command in a terminal, it asks once:
Help improve Pilum? Share anonymous build telemetry: which recipe and stepran, whether it succeeded, how long it took. Never service names, projectIDs, paths or error messages. Details: https://pilum.dev/docs/reference/telemetry/
Share telemetry? [y/N]Pressing Enter means no. Either answer is saved, so it never asks again. It doesn’t ask in CI, when input or output isn’t a terminal, with --json or --quiet, or for help, --version and completion.
pilum telemetry status # on or off, and which setting decided itpilum telemetry enablepilum telemetry disable # also deletes the install IDpilum telemetry last # print the exact payload last sentThe setting lives in your user config directory (~/.config/pilum/config.yaml on Linux, ~/Library/Application Support/pilum/config.yaml on macOS), not in your project.
Environment variables take precedence, in this order:
| Setting | Effect |
|---|---|
DO_NOT_TRACK=1 | Always off, whatever else is set |
PILUM_TELEMETRY=1 or 0 | On or off for that shell or job |
| The saved setting | From the prompt or pilum telemetry enable/disable |
| Nothing set | Off |
In GitHub Actions, the action turns telemetry on unless you set its telemetry input to false. Each job that sends prints a line saying so. To turn it off:
- uses: SID-Technologies/pilum-action@v1 with: command: deploy telemetry: falseA job-level PILUM_TELEMETRY=0 or DO_NOT_TRACK=1 turns it off too. Running the CLI yourself in CI, without the action, sends nothing unless PILUM_TELEMETRY=1 is set.
What is sent
Only deploy, build, publish, push and rollback send anything. check, list, status, dry-run and the other read-only commands never do.
Each run sends one pilum_run event:
| Property | Example | Meaning |
|---|---|---|
version | v0.11.0 | Your Pilum version (dev for local builds) |
os, arch | linux, amd64 | Operating system and CPU architecture |
command | deploy | Which command ran |
ci, ci_provider | true, github_actions | Whether it ran in CI, and which (GitHub Actions, GitLab, Buildkite, CircleCI, Jenkins, other) |
success | false | Whether the command succeeded |
exit_class | deploy | The exit code’s category: ok, config, no_services, deploy, io, invalid_args, lock, general |
duration_ms | 184230 | How long the command took |
services | 7 | How many services it ran on |
recipes | {"gcp-cloud-run": 5, "npm": 2} | Built-in recipe names with counts |
flags | {"dry_run": false, "only_changed": true} | Whether --dry-run, --only-changed, --no-deps, --no-warm and --force were set |
Each failed step adds a pilum_step_failed event with version, os, arch, ci and ci_provider, plus:
| Property | Example | Meaning |
|---|---|---|
recipe | gcp-cloud-run | The built-in recipe the step belongs to |
step | deploy to cloud run | The built-in step that failed |
error_class | permission | auth, permission, quota, timeout, not_found, network, build_failed or unknown |
duration_ms | 41200 | How long the step ran before it failed |
error_class is worked out on your machine by looking for words like “403” or “quota exceeded” in the error. The error text itself never leaves your machine.
Events also carry a random install ID, created when you opt in and deleted when you opt out. It identifies an install, not you or your machine. On a CI runner with no saved setting, each job gets its own.
What is never sent
- Service names, project IDs, regions, registries, image names or tags
- Git SHAs, branch names or repository names
- File paths or environment variables
- Error messages or command output
- The names of custom recipes or steps. Anything loaded with
--recipe-path, and any step that isn’t part of a built-in recipe, is reported ascustom.
Where it goes
Events go to https://t.pilum.dev/v1/events, a small Cloudflare Worker whose code is public in SID-Technologies/Pilum-Metrics-Worker. It drops anything outside a fixed allowlist of events and values, never reads or stores your IP address, and forwards the rest to PostHog as anonymous events: no person profile and no location.
Sending happens once, after the command finishes, and gives up after one second. A slow or unreachable endpoint never delays or fails a deploy, and never changes Pilum’s exit code.
If we ever need to stop collection, the endpoint tells Pilum so, and Pilum stops sending for a week.
Checking what was sent
pilum telemetry last prints the exact JSON body of the last request, for example:
{ "schema": 1, "install_id": "4075ed6b-8e07-4fbf-ac46-aee74d0b9086", "sent_at": "2026-10-26T06:15:23Z", "events": [ { "event": "pilum_run", "ts": "2026-10-26T06:15:23Z", "props": { "arch": "arm64", "ci": false, "ci_provider": "none", "command": "deploy", "duration_ms": 10, "exit_class": "ok", "flags": { "dry_run": true, "force": false, "no_deps": false, "no_warm": false, "only_changed": false }, "os": "darwin", "recipes": { "gcp-cloud-run": 7 }, "services": 7, "success": true, "version": "v0.11.0" } } ]}