Important: This documentation is about an older version. It's relevant only to the release noted, many of the features and functions have been updated or replaced. Please view the current version.
Understand gcx usage statistics
gcx reports limited usage statistics about itself to Grafana Labs. This data is used to understand which commands and flags are used most, where commands fail, and which commands people try that don’t exist, so we can make the product better.
The statistics describe only the shape of usage, including command path, and flag names. Positional argument values, free-form flag values, and resource names are never sent, and the flags you set are recorded by name only. Numeric values such as exit code, duration, and HTTP status describe command execution or protocol behavior. No raw count of batch or resource volume is sent.
For the resource commands that operate on batches, the size of the operation is sent as one of seven fixed categories rather than as a number. Two of those categories, 0 and 1, cover a single value each, so those two sizes are exact; every larger category is a range. See How to read the batch fields.
Further fields describe how the command ran rather than naming a flag: output_format records the output format used, from a fixed list of known formats; dry_run records whether the operation executed in dry-run mode; and grafana_auth_method records the authentication category selected for the Grafana connection, from a fixed vocabulary and never the credential itself. output_format is read from --output, which means a command that renders JSON because you passed --json is still recorded with --output’s value; that mismatch is a known bug rather than intended behaviour. dry_run is derived from the operation and is set even for commands that have no --dry-run flag; grafana_auth_method is described in Failure and authentication fields. Some server-side enrichment is also performed on the usage statistics exported - see Server-side enrichment for details.
Note
Usage statistics reporting is enabled by default. See the Opt out section below for guidance on how to turn off usage reporting.
Telemetry data and identifiers
The only identifier is a device_id field: a randomly generated UUID created on first use and stored at $XDG_STATE_HOME/gcx/device-id. It identifies an installation of gcx, not a person. It’s random, not derived from your hardware or account.
Understand which data is collected
Each gcx event contains the following properties:
When the invocation is a batch resource operation (gcx resources push, pull, delete, or validate) that ran to completion, these additional fields are set. They describe the size of the operation, never what it contained:
The seven categories are exactly 0, 1, 2-5, 6-20, 21-100, 101-1000, and 1001+. Note that 0 and 1 are singleton categories, so those two sizes are recoverable exactly; every larger category is a range.
Sizes are sent as categories rather than as numbers on purpose. An exact count of a large batch, correlated with the per-installation device_id and the network organization name added on receipt, would describe a specific organization’s resource inventory. Categories answer how gcx is used without carrying that detail, and the two singleton categories carry no inventory to infer. No raw numeric count field is sent.
How to read the batch fields
These fields are easy to misread, so the following constraints are part of the contract:
- All four are present together, or none are. Their absence means the invocation was not one of those four commands, or it stopped before the operation reached a final count.
0means nothing was counted in that outcome, which is not the same as nothing having happened. The three counts are not a complete partition of the work: a resource filtered out before processing is recorded in none of them.gcx resources validateover resources that are all managed elsewhere, orgcx resources deleteagainst a resource type whose API does not support deletion, can therefore report0/0/0for a run that did examine resources. A0is still distinct from the field being absent, which means the operation never reached a final count.- The sizes describe the operation, not the output. They are recorded once the operation has finished and its counts are final. If the summary then fails to render or cannot be written to stdout, the sizes are still reported, because work that already happened is not undone by a display failure. Equally, they do not always correspond to a number printed on screen:
--jqand--json <fields>reshape the output, andvalidateprints only failures and skips in JSON. - An operation that aborted partway reports nothing. It never reached a final count, so there is no size to report. Note that
gcx resources deletewith--on-error=abortmay have deleted some resources before stopping; that partial work is deliberately not reported, so absence consistently means “no final count”, never “no work done”. - The unit depends on the command, so these must not be compared or totalled across commands.
gcx resources pullis the clearest case, and its failure count is genuinely mixed: a resource fetched but not written to disk counts as one failure, and a whole resource type whose list call fails also counts as one failure. Skips there count whole resource types the server could not list. A pull failure count of 2 can therefore mean two resources or two entire types. batch_skipped_bucketmeans different things per command, and whether it measures anything depends on the run. Ingcx resources pushandgcx resources deletea skip is recorded solely when a dry run cannot be verified server-side, so a run without--dry-runreports0by construction rather than as a measurement, while a dry run reports a real count.gcx resources validateis always a dry run, so its skip count is a genuine measurement on every run, and a non-zero value there is a normal outcome rather than a sign of trouble. Ingcx resources pullit is also a genuine measurement on every run, counting resource types the server could not list.dry_runis not a mutation flag.gcx resources validatealways reportstrueandgcx resources pullalwaysfalse, yet neither changes anything: pull is read-only. Readdry_runtogether withcommand, never as “this run modified resources”.gcx resources getnever reports these fields, because only the four commands listed above are instrumented. It is a read, but so ispull, which does report.
Canceled invocations
An invocation that stopped before it finished reports outcome: canceled with exit_code: 5, and error_kind present but empty, because a stop is not a kind of failure. No property is collected specifically for cancellation — and the failure fields described in Failure and authentication fields are never attached to one, for the same reason. Like every other event it is sent on a best-effort basis. For an invocation you interrupted, pressing Ctrl-C a second time ends the process immediately, before the report is sent; an invocation that stopped for another reason waits out the report like any other run, because there is no interrupt for the second Ctrl-C to follow.
Three things this value does not tell you:
- It is not always your Ctrl-C. Any invocation whose exit code is
5reportscanceled, which includes a confirmation prompt you declined and a task the server itself reported as canceled. The field records that the invocation stopped early, not who stopped it. - Not every interrupted command reports it. Commands that treat an interrupt as a clean shutdown —
gcx dev serve, for example — finish normally when you press Ctrl-C, so they reportokwithexit_code: 0like any other successful run. - Only Ctrl-C is caught.
gcxinstalls a handler forSIGINTalone. ASIGTERMor aSIGKILLends the process at once, before any report is built, so the invocation reports nothing at all. Orchestrators and CI runners usually stop a process withSIGTERM, socanceledundercounts the invocations that stopped early in those environments.
If your first-ever gcx command is one you interrupt, the notice described in The one-time notice is printed after the interrupt, because that invocation does report. The notice comes first and the export is attempted after it, so the notice records the attempt rather than a delivery: as above, the report is best-effort and may never arrive.
This moves the denominator of every outcome rate in two ways, so compare rates within a version rather than across the version where canceled first appears:
- Some invocations start being counted at all. Earlier versions reported nothing for an invocation that ended on the interrupt path — exit code
5with no error printed. Those now count towards the total, so the share ofokinvocations falls without anything having got worse. This is narrower than “every Ctrl-C”: an interrupt a command turns into a clean shutdown was always reported asok, and one that leaves a batch partially applied was always reported as a partial failure. - Some invocations change label. Exit-code-
5invocations that were already reported — a declined prompt, for instance — moved out ofruntime_errorwitherror_kind: errorand intocanceledwith an emptyerror_kind. Both theruntime_errorshare and the volume oferror_kind: errordrop for the same reason, with no change in what happened.
Failure and authentication fields
For Grafana connections, gcx records the authentication category selected, such as oauth, token, basic, mtls, anonymous, or unknown, but never credentials. For some failed commands, it may also record a 4xx/5xx HTTP status or a fixed Kubernetes reason category; these details are omitted for partial failures and cancellations.
The k8s_reason vocabulary is exactly: Unauthorized, Forbidden, NotFound, AlreadyExists, Conflict, Gone, Invalid, BadRequest, MethodNotAllowed, NotAcceptable, RequestEntityTooLarge, UnsupportedMediaType, Expired, Timeout, ServerTimeout, TooManyRequests, InternalError, ServiceUnavailable, StorageReadError, plus the other sentinel.
These fields are easy to misread, so the following constraints are part of the contract:
http_statusis a transport status, never a body status. Datasource queries can fail inside an HTTP 200 response, with the real error and its status embedded in the body. Such a failure sets nohttp_statusat all. In exactly that caseerror_kindmay still sayauth_failure, because the command classifies and explains errors on the embedded status; the divergence is deliberate and must not be “fixed” by aligning either side.- Only failure statuses travel. A status outside
400–599— a success, a redirect, anything malformed — is omitted, never coerced into the range. - Coverage is partial, so absence proves nothing.
http_statusis recorded where the failing client surfaces a machine-readable status: provider APIs that use the shared error helper, datasource query endpoints, datasource management commands, Knowledge Graph, Adaptive Logs, andgcx api. Clients that report failures as plain text — GCOM, Fleet, k6, Faro, Adaptive Metrics, and the assistant among them — are not instrumented, so the absence ofhttp_statusmust never be read as “no HTTP failure occurred”. - Kubernetes failures report a reason, never a status. A Kubernetes API error sets
k8s_reasonand leaveshttp_statusempty; the status code inside a Kubernetes error never feedshttp_status. - Both failure fields are omitted for exit codes
4and5. A partial failure has no single causal status — the one that surfaced would stand in for many — and a canceled run is not a failure. grafana_auth_methoddescribes Grafana connection authentication only, never Grafana Cloud/GCOM authentication. Absent means the invocation never resolved a Grafana connection, or that it resolved several different methods (gcx config checkexamines every context, so it reports no single method when they differ).anonymousmeans a valid selection with no credential material — requests went out unauthenticated.unknownmeans the configured method could not be classified. A selected method whose credential is invalid or rejected reports the method: which authentication category fails is precisely what this field exists to show.grafana_auth_methodis present on every outcome — success, failure, partial failure, and cancellation — whenever the invocation resolved a Grafana connection. It follows that resolution rather than the configuration, so here too absence proves nothing: a command that never builds a Grafana connection, as Grafana Cloud–only commands do not, reports no method even under a fully configured context.gcx loginreports the method it actually resolved by probing, even when the login fails after authentication was resolved.
Parse-failure fields
When the invocation fails to parse, these additional fields are set. They capture what was attempted so the team can understand the differences between what users expect and what exists. They are not populated yet: a parse failure currently reports no event at all (see Invocations that report nothing), and outcome is never parse_error today.
Invocations that report nothing
Some invocations never emit an event:
- Shell completion — the completion machinery runs on every tab-press and carries no usage signal.
gcx version- Invocations that failed to parse — an unknown command or flag reports nothing today, which is why the
parse_error_*properties above are not yet populated.
How the report is sent
One invocation sends at most one report. gcx does not batch events, does not queue them for a later run, and does not start a background process that outlives the command.
The report is sent synchronously, so the invocation can take up to one extra second before it exits. In normal conditions the cost is much smaller, and an endpoint that refuses the connection or fails to resolve costs almost nothing, because the failure is immediate.
One case does cost the full second on every invocation: a network that drops the packets silently instead of refusing them. Some corporate firewalls do this. If you block the destination that way, each gcx invocation waits out the one-second limit before it exits. To avoid the delay, opt out with GCX_TELEMETRY=disabled instead of blocking the address. An opted-out invocation builds no event and opens no connection.
To send the report somewhere else, set GCX_TELEMETRY_ENDPOINT to another URL. This changes the destination only. It is not an opt-out, and the value is used as given.
Server-side enrichment
Reports are received by Grafana’s usage-stats service, the same service that receives usage reports from Grafana, Loki, Tempo, and Mimir. On receipt, the service adds two pieces of information derived from the connection:
- A coarse geographic region (for example, a country or subdivision), taken from headers added by the CDN edge.
- The network organization name from a whois lookup of the connecting IP address. For CLI traffic this typically resolves to your ISP or employer’s network.
The connecting IP address is not stored in the usage event.
Inspect what would be sent
To see exactly what gcx would report for an invocation, set GCX_TELEMETRY=log. The event is printed to stderr and nothing is sent:
GCX_TELEMETRY=log gcx dashboards listOpt out
You can control usage statistics reporting three ways:
GCX_TELEMETRYenvironment variable: Set toenabled,disabled, orlog. Takes precedence over everything else:
export GCX_TELEMETRY=disabledDO_NOT_TRACKenvironment variable: Set to1ortrueto disable reporting, following the cross-tool DO_NOT_TRACK convention. Overridden byGCX_TELEMETRY.Configuration file: Add a top-level
diagnosticsblock to yourgcxconfiguration file, withtelemetryset toenabled,disabled, orlog:
diagnostics:
telemetry: disabledOpting out disables reporting entirely. No event is constructed and nothing is sent.
The one-time notice
gcx prints a short notice to stderr the first time it reports an invocation. The notice states what is collected and how to opt out. It is printed after the command’s own output, so it never mixes into a result document on stdout.
The notice is shown at most once for each revision of its text. gcx records the revision it showed in $XDG_STATE_HOME/gcx/telemetry-notice-shown, which is ~/.local/state/gcx/telemetry-notice-shown on most systems. Delete that file to see the notice again. When the text changes in a material way, the revision changes with it, and the notice is shown once more — including to installations that already ran gcx.
Two limits are worth stating plainly:
- The notice is only printed to an interactive terminal. It is skipped when stderr is not a terminal, when
gcxdetects a CI environment, and whengcxruns in agent mode. Those invocations still report. So a CI job or a coding agent can report usage statistics without the notice ever appearing. If you rungcxin either environment, opt out in the configuration file or in the environment of the job. - The first report is sent by the same invocation that prints the notice. The notice comes first and the report follows, in one process. So reading the notice tells you that one invocation has already reported. Every invocation after it obeys the opt-out you choose.


