Skip to content

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 step
ran, whether it succeeded, how long it took. Never service names, project
IDs, 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.

Terminal window
pilum telemetry status # on or off, and which setting decided it
pilum telemetry enable
pilum telemetry disable # also deletes the install ID
pilum telemetry last # print the exact payload last sent

The 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:

SettingEffect
DO_NOT_TRACK=1Always off, whatever else is set
PILUM_TELEMETRY=1 or 0On or off for that shell or job
The saved settingFrom the prompt or pilum telemetry enable/disable
Nothing setOff

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: false

A 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:

PropertyExampleMeaning
versionv0.11.0Your Pilum version (dev for local builds)
os, archlinux, amd64Operating system and CPU architecture
commanddeployWhich command ran
ci, ci_providertrue, github_actionsWhether it ran in CI, and which (GitHub Actions, GitLab, Buildkite, CircleCI, Jenkins, other)
successfalseWhether the command succeeded
exit_classdeployThe exit code’s category: ok, config, no_services, deploy, io, invalid_args, lock, general
duration_ms184230How long the command took
services7How 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:

PropertyExampleMeaning
recipegcp-cloud-runThe built-in recipe the step belongs to
stepdeploy to cloud runThe built-in step that failed
error_classpermissionauth, permission, quota, timeout, not_found, network, build_failed or unknown
duration_ms41200How 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 as custom.

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"
}
}
]
}