| 1 | --- |
| 2 | layout: ../layouts/Marketing.astro |
| 3 | title: "Privacy & Telemetry — Alchemy" |
| 4 | description: What data the Alchemy CLI and Cloudflare State Store collect, where it goes, and how to opt out. |
| 5 | --- |
| 6 | import "../styles/privacy.css"; |
| 7 | |
| 8 | <div class="alc-privacy"> |
| 9 | |
| 10 | # Privacy & Telemetry |
| 11 | |
| 12 | The Alchemy CLI and the Cloudflare State Store worker emit |
| 13 | OpenTelemetry traces, metrics, and logs to a relay we operate at |
| 14 | `otel.alchemy.run`, which forwards to our Axiom workspace. The data |
| 15 | helps us prioritize providers, find bugs, and understand which |
| 16 | features people actually use. |
| 17 | |
| 18 | This page documents exactly what is collected, what is not, where |
| 19 | it goes, and how to opt out. Source of truth is the code: |
| 20 | [`Telemetry/Attributes.ts`](https://github.com/alchemy-run/alchemy/blob/main/packages/alchemy/src/Telemetry/Attributes.ts) |
| 21 | and [`Cloudflare/StateStore/Api.ts`](https://github.com/alchemy-run/alchemy/blob/main/packages/alchemy/src/Cloudflare/StateStore/Api.ts). |
| 22 | |
| 23 | ## What we collect |
| 24 | |
| 25 | ### From the CLI |
| 26 | |
| 27 | Resource attributes attached to every span/metric/log: |
| 28 | |
| 29 | | Attribute | Value | |
| 30 | | --- | --- | |
| 31 | | `alchemy.user.id` | Random UUID generated on first run, persisted at `~/.alchemy/id`. Not your shell user, email, or hostname. | |
| 32 | | `alchemy.session.id` | Random UUID per CLI invocation. | |
| 33 | | `alchemy.version` | The version of the `alchemy` package you're running. | |
| 34 | | `alchemy.git.root_commit` | SHA of the first commit in your repo. Acts as a stable repo fingerprint. | |
| 35 | | `alchemy.git.origin_hash` | SHA-256 of `git config remote.origin.url`. The raw URL is never sent. | |
| 36 | | `alchemy.git.branch_hash` | SHA-256 of the current branch name. | |
| 37 | | `alchemy.runtime.name` / `.version` | `bun` / `node` / `deno` and its version. | |
| 38 | | `alchemy.ci` / `alchemy.ci.provider` | Whether this looks like a CI run, and which provider (GitHub Actions, GitLab, etc.). | |
| 39 | | `host.arch`, `os.type`, `os.version`, `host.cpus`, `host.memory_mb` | Coarse machine info. | |
| 40 | |
| 41 | Per-operation span attributes: |
| 42 | |
| 43 | - `cli.<command>` spans carry the command name (`deploy`, `destroy`, `plan`, ...) and outcome (`success` / `error`). |
| 44 | - `provider.<op>` spans carry the resource type (`AWS.S3.Bucket`, `Cloudflare.Workers.Worker`, ...), op (`create` / `update` / `delete` / `read`), and outcome. |
| 45 | - `state_store.init` spans carry the state-store backend slug (`local`, `inmemory`, `http`, `cloudflare-http`, or any third-party slug). |
| 46 | |
| 47 | ### From the Cloudflare State Store |
| 48 | |
| 49 | When you deploy the [Cloudflare State Store](/state-store/custom-state-store) it runs as a Worker on **your** Cloudflare account. The worker emits OTLP traces/metrics/logs to the same `otel.alchemy.run` relay. |
| 50 | |
| 51 | Resource attributes (set on every signal from the worker): |
| 52 | |
| 53 | | Attribute | Value | |
| 54 | | --- | --- | |
| 55 | | `service.name` | `alchemy-state-store` | |
| 56 | | `service.version` | The deployed worker contract version (currently `2`). | |
| 57 | | `alchemy.state_store.script_name` | `alchemy-state-store`. | |
| 58 | |
| 59 | Per-request span attributes (one per state-store API call): |
| 60 | |
| 61 | | Attribute | Value | |
| 62 | | --- | --- | |
| 63 | | `alchemy.state_store.op` | `getState` / `setState` / `deleteState` / `listStacks` / `listStages` / `listResources` / `getReplacedResources` / `deleteStack` / `getVersion` | |
| 64 | | `alchemy.state_store.stack` | The stack name from your `alchemy.run.ts` (e.g. `MyApp`). | |
| 65 | | `alchemy.state_store.stage` | The stage name (e.g. `prod`, `dev_alice`). | |
| 66 | | `alchemy.state_store.fqn` | The fully-qualified resource name within the stack (e.g. `bucket/uploads`). | |
| 67 | | `duration` | Op latency. | |
| 68 | |
| 69 | CLI-side spans that touch the Cloudflare State Store |
| 70 | (`state_store.bootstrap`, `state_store.deploy`, `state_store.init`) |
| 71 | also carry `alchemy.cloudflare.account_hash` — a SHA-256 of your |
| 72 | Cloudflare account ID. The raw account ID is never sent. We use the |
| 73 | hash to count distinct deployments without identifying anyone. |
| 74 | |
| 75 | ## What we don't collect |
| 76 | |
| 77 | - Secrets, API tokens, environment variables, `.env` contents. |
| 78 | - Resource property values, source code, file contents. |
| 79 | - Raw filesystem paths, hostnames, or shell user names. |
| 80 | - Raw git URLs, raw branch names, raw Cloudflare account IDs (only SHA-256 hashes of these). |
| 81 | - IP addresses (the relay strips them before forwarding). |
| 82 | - Any tracking cookies, advertising IDs, or third-party identifiers. |
| 83 | |
| 84 | ## Where it goes |
| 85 | |
| 86 | `otel.alchemy.run` is a Cloudflare Worker we operate. It accepts |
| 87 | standard OTLP/HTTP at `/v1/traces`, `/v1/logs`, and `/v1/metrics`, |
| 88 | attaches an Axiom ingest token server-side, and forwards to our |
| 89 | Axiom workspace. Source for the relay lives at |
| 90 | [`stacks/otel/Ingester.ts`](https://github.com/alchemy-run/alchemy/blob/main/stacks/otel/Ingester.ts). |
| 91 | |
| 92 | We do not share or sell the data. Aggregated stats may appear in |
| 93 | public talks, posts, or dashboards. |
| 94 | |
| 95 | ## How to opt out |
| 96 | |
| 97 | ### CLI — kill all telemetry |
| 98 | |
| 99 | Any one of these disables the OTLP exporter entirely. The CLI's |
| 100 | default tracer becomes a no-op, so nothing leaves your machine: |
| 101 | |
| 102 | ```sh |
| 103 | DO_NOT_TRACK=1 alchemy deploy |
| 104 | NO_TRACK=1 alchemy deploy |
| 105 | ALCHEMY_TELEMETRY_DISABLED=1 alchemy deploy |
| 106 | ``` |
| 107 | |
| 108 | To make the opt-out persistent without setting an env var on every |
| 109 | run, create the marker file: |
| 110 | |
| 111 | ```sh |
| 112 | mkdir -p ~/.alchemy |
| 113 | echo "true" > ~/.alchemy/telemetry-disabled |
| 114 | ``` |
| 115 | |
| 116 | To re-enable, delete the file: |
| 117 | |
| 118 | ```sh |
| 119 | rm ~/.alchemy/telemetry-disabled |
| 120 | ``` |
| 121 | |
| 122 | ### Cloudflare State Store — opt out per-deployment |
| 123 | |
| 124 | The state-store worker exports its own OTLP signals — these go |
| 125 | through the same relay but flow even if your CLI's telemetry is |
| 126 | disabled (because the worker runs on your CF account, not your |
| 127 | machine). Two mechanisms exist: |
| 128 | |
| 129 | **`noTrack` option.** Passes through to the CLI-side state-store |
| 130 | spans, suppressing the `alchemy.cloudflare.account_hash` attribute: |
| 131 | |
| 132 | ```ts |
| 133 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 134 | |
| 135 | Cloudflare.state({ noTrack: true }); |
| 136 | ``` |
| 137 | |
| 138 | **`NO_TRACK` env var.** When `Cloudflare.state()` is called without |
| 139 | an explicit `noTrack`, it falls back to `NO_TRACK` from the |
| 140 | environment. |
| 141 | |
| 142 | ```sh |
| 143 | NO_TRACK=1 alchemy deploy |
| 144 | ``` |
| 145 | |
| 146 | `noTrack` only suppresses the account-hash attribute on state-store |
| 147 | spans. To stop the worker from emitting signals at all, you'd need |
| 148 | to remove the OTLP layer in your own fork of `Api.ts`, or run a |
| 149 | custom HTTP state store via [/state-store/custom-state-store](/state-store/custom-state-store). |
| 150 | |
| 151 | ## Questions |
| 152 | |
| 153 | Email [sam@alchemy.run](mailto:sam@alchemy.run) or open an issue at |
| 154 | [alchemy-run/alchemy](https://github.com/alchemy-run/alchemy/issues). |
| 155 | |
| 156 | </div> |