# How RTK Anonymous Telemetry Works and Ensures GDPR Compliance

> Learn how RTK anonymous telemetry works, collecting only aggregate metrics with opt-in consent and automated erasure for full GDPR compliance in Rust.

- Repository: [rtk-ai/rtk](https://github.com/rtk-ai/rtk)
- Tags: deep-dive
- Published: 2026-04-24

---

**RTK implements a privacy-first, opt-in telemetry system that sends a single daily ping containing only aggregate, non-identifiable metrics, with explicit consent management and automated data erasure mechanisms built directly into the Rust source code.**

The `rtk-ai/rtk` CLI tool collects usage insights through a **privacy-by-design telemetry architecture** that strictly adheres to **RTK anonymous telemetry GDPR compliance** standards. Unlike conventional analytics, this system operates on an explicit opt-in basis, transmitting a minimal payload—hashed device identifiers, version strings, and aggregate counts—no more than once every 23 hours. Understanding how this balances data utility with legal requirements requires examining the specific Rust implementations in [`src/core/telemetry.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/telemetry.rs) and the user-facing controls in [`src/core/telemetry_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/telemetry_cmd.rs).

## Telemetry Initialization and Rate Limiting

The telemetry lifecycle begins with the **`maybe_ping()`** function in **[[`src/core/telemetry.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/telemetry.rs)](https://github.com/rtk-ai/rtk/blob/master/src/core/telemetry.rs#L18-L66)**, which executes at the start of every CLI invocation. This function implements a cascade of **early-exit guards** to ensure zero network activity unless all strict conditions are met.

### Compile-Time and Environment Controls

RTK provides two layers of hard disabling that prevent telemetry compilation or execution:

- **Compile-time URL check**: The code checks `if TELEMETRY_URL.is_none() { return; }` using `option_env!`. If the `RTK_TELEMETRY_URL` environment variable is not present during build, the telemetry module returns immediately, guaranteeing **zero network activity** in the resulting binary.
- **Runtime environment override**: Users can block telemetry without modifying configuration files by setting `RTK_TELEMETRY_DISABLED=1`. The function checks `std::env::var("RTK_TELEMETRY_DISABLED")` and returns early if set.

### Explicit Consent and Configuration Gates

Before any network request, the system verifies user intent through the configuration struct loaded from [`config.toml`](https://github.com/rtk-ai/rtk/blob/main/config.toml):

1. **Consent flag**: `cfg.telemetry.consent_given` must be `Some(true)`. If `None` (never asked) or `Some(false)`, the function returns immediately.
2. **Enable flag**: Even after consent, `cfg.telemetry.enabled` must be `true`, allowing users to disable collection without revoking consent formally.

### The 23-Hour Rate Limiter

To minimize data volume (GDPR Article 5 data minimisation), RTK limits pings to one per day using a marker file stored at `~/.local/share/rtk/.telemetry_marker`. The code compares the file’s modification time against the constant **`PING_INTERVAL_SECS = 23 * 60 * 60`** (23 hours), ensuring at most one ping per daily cycle.

## Device Anonymization and Payload Structure

RTK ensures **device-level anonymity** by cryptographically hashing identifiers before transmission, making re-identification computationally infeasible.

### Per-Device Salt Generation

Upon first telemetry-enabled run, RTK generates a **64-character hexadecimal random salt** stored at `~/.local/share/rtk/.device_salt` (path determined by **`salt_file_path()`** in [`src/core/telemetry.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/telemetry.rs)). This salt persists for the device’s lifetime but is never transmitted.

The telemetry payload uses **`SHA-256(salt)`** as the `device_hash`. Because the salt is random and never leaves the local machine, the hash cannot be reverse-engineered to identify the user or hardware.

### Aggregate-Only Data Collection

The **`send_ping()`** function constructs a JSON payload containing strictly non-personal fields. As documented in **[[`docs/TELEMETRY.md`](https://github.com/rtk-ai/rtk/blob/main/docs/TELEMETRY.md)](https://github.com/rtk-ai/rtk/blob/master/docs/TELEMETRY.md#what-is-not-collected)**, the following data is **never** collected: command arguments, file paths, repository URLs, environment variables, or hostnames.

The payload includes only aggregate metrics:

```json
{
  "device_hash": "a3f8c9…",
  "version": "0.34.1",
  "os": "macos",
  "arch": "aarch64",
  "install_method": "homebrew",
  "commands_24h": 142,
  "top_commands": ["git","cargo","ls"],
  "tokens_saved_24h": 450000,
  "hook_type": "claude",
  "custom_toml_filters": 3
}

```

Usage statistics (e.g., `commands_24h`) are aggregated locally in a SQLite database (`tracking.db`) by [`src/core/tracking.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/tracking.rs) before being included in the telemetry payload.

## Non-Blocking Delivery and Security

To ensure CLI responsiveness, the ping dispatch occurs in a **background thread** spawned via `std::thread::spawn`. The HTTP request uses a **2-second timeout** (`req.timeout(Duration::from_secs(2))`) to prevent hanging if the telemetry server is unreachable. All network traffic uses **HTTPS**, and endpoint authentication tokens are injected at compile time via `option_env!`, ensuring no secrets ship inside the binary.

## User Control Commands and Erasure

RTK exposes granular control through the `telemetry` subcommand implemented in **[[`src/core/telemetry_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/telemetry_cmd.rs)](https://github.com/rtk-ai/rtk/blob/master/src/core/telemetry_cmd.rs)**.

### Checking and Modifying Consent

- **`rtk telemetry status`**: Displays consent state (`yes/no/never asked`), the enabled flag, and a truncated `device_hash` (first and last 8 characters) for verification.
- **`rtk telemetry enable`**: Launches an interactive prompt calling `save_telemetry_consent(true)`, which records affirmative consent with a timestamp.
- **`rtk telemetry disable`**: Calls `save_telemetry_consent(false)`, immediately halting data collection without deleting historical data.

### Right to Erasure Implementation

The **`rtk telemetry forget`** command implements GDPR Article 17 (right to erasure) through the **`send_erasure_request()`** function:

1. **Local deletion**: Removes `~/.local/share/rtk/.device_salt`, the telemetry marker file, and the local `tracking.db` SQLite database.
2. **Server request**: Sends a `POST /erasure` request containing only the `device_hash` and action type.
3. **Fallback mechanism**: If the server request fails, the CLI prints the device hash and instructs the user to email `contact@rtk-ai.app` with the hash to complete manual deletion.

```bash

# Complete data removal workflow

rtk telemetry forget

# Output:

# • Deleting local telemetry salt... done

# • Removing tracking database... done

# • Sending erasure request to server... done

# If server unreachable:

# "To complete erasure, email contact@rtk-ai.app with your device hash: a3f8c9..."

```

## Data Retention and Compliance Policies

RTK’s retention policies align with GDPR Article 5 (storage limitation) and are documented in [`docs/TELEMETRY.md`](https://github.com/rtk-ai/rtk/blob/main/docs/TELEMETRY.md):

| Data Location | Retention Period | GDPR Basis |
|---------------|------------------|------------|
| Server-side telemetry records | **12 months** (automated purge) | Article 5(1)(e) |
| Server erasure audit logs (IP addresses) | **6 months** then anonymised | Article 5(1)(c) |
| Client-side SQLite (`tracking.db`) | **90 days** (configurable in [`config.toml`](https://github.com/rtk-ai/rtk/blob/main/config.toml)) | User control |
| Local salt files | Until `rtk telemetry forget` executes | Article 17 |

Contributors adding new telemetry fields must follow the **aggregate-only checklist** in [`docs/TELEMETRY.md`](https://github.com/rtk-ai/rtk/blob/main/docs/TELEMETRY.md) to prevent accidental collection of personal data.

## Summary

- **Opt-in by design**: Telemetry requires explicit `rtk telemetry enable` consent; absence of consent blocks all network activity.
- **Rate limited**: The `maybe_ping()` function enforces a maximum of one ping per 23 hours via marker file checks.
- **Cryptographically anonymous**: Device identity is protected via `SHA-256` hashing of a local 64-hex salt; no raw identifiers leave the machine.
- **Non-identifiable payloads**: Only aggregate counts (commands, tokens saved) and system metadata (OS, arch, version) are transmitted.
- **User sovereignty**: `rtk telemetry forget` triggers automated local deletion and server-side erasure requests, fulfilling GDPR Article 17 requirements.
- **Secure transmission**: HTTPS with compile-time injected credentials and strict request timeouts prevent data leakage or CLI blocking.

## Frequently Asked Questions

### What specific data does RTK collect in its telemetry ping?

The ping contains only **aggregate, non-personal metrics** such as `device_hash` (SHA-256 of local salt), RTK version, operating system, architecture, install method, total command counts over 24 hours, top-level command names (e.g., "git", "cargo"), tokens saved, and hook configuration types. According to [`docs/TELEMETRY.md`](https://github.com/rtk-ai/rtk/blob/main/docs/TELEMETRY.md), RTK never collects command arguments, file paths, repository URLs, or environment variables.

### How can I verify that telemetry is completely disabled before running RTK?

You can verify zero network activity by checking **`rtk telemetry status`**, which reports the consent state, enabled flag, and environment override status. Additionally, if RTK was compiled without the `RTK_TELEMETRY_URL` environment variable set, the binary contains no telemetry endpoint at all, as the `TELEMETRY_URL` constant evaluates to `None` at compile time in [`src/core/telemetry.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/telemetry.rs).

### What happens to my data when I run `rtk telemetry forget`?

This command triggers a complete erasure workflow: it deletes your local device salt (`~/.local/share/rtk/.device_salt`), removes the telemetry marker file and local SQLite database (`tracking.db`), and sends a `POST /erasure` request to the server containing only your device hash. If the server request fails, the CLI displays your device hash and instructs you to email `contact@rtk-ai.app` to complete the GDPR Article 17 deletion manually.

### How long does RTK retain telemetry data on its servers?

Server-side telemetry records are **automatically purged after 12 months**. IP addresses in erasure audit logs are retained for **6 months** before being anonymised. Client-side usage statistics in `tracking.db` are retained for **90 days** by default, configurable via [`config.toml`](https://github.com/rtk-ai/rtk/blob/main/config.toml).