Files
software-mansion__argent/Telemetry.md
Ignacy Łątka 0a6dbec87c feat(telemetry): export events via OpenTelemetry OTLP logs instead of PostHog (#570)
## What

Swaps the `posthog-node` transport for an OpenTelemetry Logs exporter.
Everything above the transport stays as it is - opt-out consent,
per-machine identity and fingerprint, the sanitizer allowlist, the typed
event surface, crash diagnostics. Only the ~3-method client boundary
changes.

One analytics event becomes one OTLP/HTTP log record:

| PostHog | OpenTelemetry |
| --- | --- |
| `capture({ event, distinctId, properties })` | `logger.emit({ body:
event, attributes: { distinct_id, "event.name", ...props } })` |
| event name | log-record `body` |
| `distinctId` | `distinct_id` attribute |
| event/base properties | log-record attributes |
| `client.shutdown()` | `LoggerProvider.shutdown()` (force-flush +
teardown) |

Records go to a hard-coded Software Mansion collector. The endpoint and
the authorization header are passed to the exporter explicitly in code,
and an explicit value beats the corresponding `OTEL_EXPORTER_OTLP_*`
variable - the same anti-exfiltration property the fixed PostHog host
had.

## Changes

- `packages/telemetry/src/posthog.ts` becomes `otel.ts`:
`LoggerProvider` + `BatchLogRecordProcessor` + `OTLPLogExporter`
(HTTP/JSON). Queue size 20 and a 10 s flush interval mirror the old
client; the per-export deadline is 1.5 s, kept at or below `index.ts`'s
drain budget so a stalled export can't hold a short-lived command open.
- Dependencies: drop `posthog-node`, add
`@opentelemetry/{api,api-logs,sdk-logs,exporter-logs-otlp-http,resources}`.
They inline cleanly into the esbuild bundles.
- Build-time ingest token via esbuild `define`
(`ARGENT_OTEL_INGEST_TOKEN`), same mechanism as `ARGENT_CLI_VERSION`.
Unset in a dev build leaves the client unconstructed and telemetry
inert.
- Base props: drop `$process_person_profile` (a PostHog directive,
meaningless to a collector) and rename `$session_id` to `session_id`.
Null-valued attributes are dropped, since OTel rejects them -
`cloud_agent` is null on the common path, and absence means the same
thing.
- `Telemetry.md`: the "who we disclose to" and "international transfers"
sections describe a Software Mansion-operated OTLP collector instead of
PostHog as a third-party processor.

## A hang this uncovered

`timeoutMillis` only bounds a request once its socket is connected,
because the exporter applies it with `req.setTimeout()`. Against a
collector address that drops packets rather than refusing them - a
corporate egress filter, a dead host behind a firewall - the socket sits
in the connecting state where no export deadline reaches it, and the
process stays alive for the OS connect timeout: 75 s past a `shutdown()`
that had already resolved at 1.75 s.

Pairing the export deadline with an agent-level socket timeout, armed at
socket creation so it covers connection establishment too, fixes it:

| collector state | before | after |
| --- | --- | --- |
| healthy | 0.18 s, delivered | 0.18 s, delivered |
| connection refused | 0.94 s | 1.03 s |
| blackholed (http and https) | 75.0 s | 1.6 s |
| connected but never responds | - | 1.6 s |

`posthog-node` exited in 10.6 s under the same blackhole (undici aborts
a connecting socket), so this was a regression as well as a hang. A unit
test that fails without the fix guards it.

## Found in review

Each of these was reproduced before it was changed, and each is pinned
by a test that fails without the fix.

- **The environment's OTLP headers were forwarded to the collector.**
The SDK merges `OTEL_EXPORTER_OTLP_HEADERS` / `_LOGS_HEADERS` into every
request, keeping any key the code does not set itself. Out in the world
that variable holds the developer's *own* observability credential - a
Honeycomb team key, a Dynatrace `Api-Token`, Grafana Cloud basic auth -
so any machine already running OpenTelemetry shipped that third-party
secret here on every batch, without it passing the sanitizer or
appearing in `Telemetry.md`. Reproduced by watching `x-honeycomb-team:
hcaik_…` arrive at a capture server. The variables are now cleared
across the exporter constructor (where the SDK resolves its header set)
and restored after.
- **`httpAgentOptions` silently disabled connection reuse.** Supplying
it replaces the agent the SDK would build, whose default is `keepAlive:
true`. Measured on loopback: three batches shared one socket by default,
and opened three sockets with `httpAgentOptions` - a fresh TCP+TLS
handshake per 10 s batch in the long-lived tool-server. `keepAlive` is
restated alongside the timeout.
- **`markDisabled()`'s drain had no grace period.** It raced flat
against `SHORT_FLUSH_TIMEOUT_MS` while `shutdown()` allowed `timeoutMs +
250`. Since the exporter's own deadline equals that budget, the flat
race fired at the same instant and always abandoned the batch instead of
letting it finish. Both call sites share one `raceDrain()` now.
- **Export failures reached nobody.** The batch processor hands errors
to OpenTelemetry's global handler, so `LoggerProvider.shutdown()`
resolves identically for a delivered batch and a rejected token - making
`shutdown()`'s `catch` unreachable. The SDK's diagnostics are now routed
into the existing debug channel under `ARGENT_TELEMETRY_DEBUG=1`; a run
without the flag stays silent so no host application's diag logger is
disturbed.
- **The build-time token now beats the `__ARGENT_OTEL_TOKEN_TEST` seam**
rather than losing to it, so the seam reaches unbundled source only.
Verified with esbuild across all three build modes.
- **The endpoint guard test mocked the exporter it guards.** It could
only show that the code passes a `url`; an SDK upgrade that changed the
precedence would have kept it green. `otel-endpoint-live.test.ts` drives
the real exporter against loopback servers - dropping the explicit
`url`, the explicit `authorization`, or the header clearing each fails
it.
- **The wire shape had no pins at all.** Bypassing the sanitizer
entirely, sending an explicit `null`, renaming `service.name` and
dropping `event.name` all left the suite green. Four tests now fail on
those mutations. The sanitizer bypass was unpinned on `main` too.

## Before this ships

1. ~~**Collector URL**~~ - done in 8bca962f. `OTLP_LOGS_ENDPOINT` is
`https://argent-otel.swmansion.com/v1/logs`. That host serves OTLP as of
2026-08-12: a `POST` answers the OTLP-shaped `401`
(`{"code":16,"message":"provided authorization does not match expected
scheme or token"}`), where it previously `301`d to `/v1/logs/` and
`404`d there. It is a distinct host from the test collector
(`167.235.245.1` vs `46.224.172.171`, its own certificate).
2. **Ingest token** - `ARGENT_OTEL_INGEST_TOKEN` has to be set in the
release build environment. Nothing under `.github/` sets it today, so
every published artifact resolves an empty token and stays inert -
silently: with no token the client is never constructed, so a run emits
nothing and writes no `telemetry-debug.log` even under
`ARGENT_TELEMETRY_DEBUG=1`. It must be the **production** host's token:
`argent-otel.swmansion.com` runs its own token list and rejects the test
collector's.
3. **IP geolocation** - PostHog's `disableGeoip: true` set
`$geoip_disable` on every event, which is what stopped location being
derived from the source IP. There is no in-repo equivalent for a
collector, so that suppression has to be configured collector-side to
keep `Telemetry.md`'s "what we collect" list accurate.
4. **Legal** - the privacy-notice edits and the version/date bump to
1.03 are drafted to match the new architecture, but Legal should review
and finalize them. Note the notice actually served at
`TELEMETRY_DETAILS_URL` is still Version 1.01 and names PostHog as the
processor.

## Verification

Unit and integration:

- `packages/telemetry`: 293 tests pass, including the rewritten
`index.test.ts`, `otel-endpoint.test.ts` and the new
`otel-endpoint-live.test.ts`.
- Every workspace suite green on the merged branch: argent-cli 276,
argent-installer 535, argent-mcp 78, argent 60, tools-client 178,
configuration-core 83, registry 87, tool-server 3070, update-core 31,
archive 11.
- `bundle-tools.cjs` leaves no `posthog` in any of the four bundles.
OTel is inlined into the three that emit - `cli-cmds.mjs`,
`installer.mjs`, `tool-server.cjs` - each carrying the endpoint and the
token define as string literals. `mcp-server.mjs` carries none: it is a
stdio shim over the tool-server, which is the process that reports.
- `lint`, `prettier --check`, `tsc --build`, the `tsconfig.test.json`
gate and `npm install --package-lock-only` (idempotent on Node 24) all
clean.
2026-08-17 21:25:51 +02:00

8.8 KiB

Argent Privacy Notice (Telemetry)

Effective date: 31 July 2026 · Version: 1.03

This notice is a product-specific supplement to the Software Mansion Privacy Policy (the "Policy") and applies to telemetry collected by Argent, a Software Mansion Software Product. Capitalised terms used but not defined here (including Personal Data, Usage Data, Legitimate Interest, EEA and Software Mansion Software Product) have the meaning given to them in the Policy. Where this notice and the Policy differ in respect of Argent telemetry, this notice prevails.

Argent collects a small amount of usage and diagnostic data ("telemetry") to help us understand how the tool is used and to make it more reliable. We have designed it to be minimal by default: we do not collect the content of your work - no source code, no file paths, no tool inputs, no application data, no error messages, and no device identifiers.

How to opt out

Telemetry is enabled by default. You can disable it at any time, and the change takes effect immediately and permanently for that installation. This is also how you exercise your right to object (see Your rights below).

argent telemetry disable

To check the current status:

argent telemetry status

Disabling telemetry does not affect any functionality of Argent.

Why we collect telemetry

We use telemetry, in our Legitimate Interest, only to:

  • understand which features are used, so we can prioritise development;
  • detect where installation, updates, or tools fail, so we can fix them;
  • measure reliability and performance (e.g. how long operations run, error rates);
  • understand the environments Argent runs in (operating system, runtime versions, terminal vs CI);
  • measure how many distinct devices use Argent, without over-counting reinstallations.

We do not use telemetry for advertising, marketing, profiling, automated decision-making, or sale of data, and we never combine it with any account or with Personal Data collected through other Services.

What we collect

Installation, update and uninstallation

  • the progress of an installation: which options were selected; whether it started, completed, failed, or was cancelled; and at which step it was cancelled;
  • the progress of an update to a newer version;
  • the progress of an uninstallation of Argent.

Tool and process usage

  • which Argent tools are invoked, and whether they succeeded or returned an error;
  • which AI coding tool is driving Argent;
  • how long a process ran in the terminal or in CI, and which Argent component emitted the event;
  • start and stop of the Argent tool-server, its uptime, the number of tools used, and the reason it stopped.

Design review (Argent Lens)

When you use the Argent Lens design-review flow (previewing and choosing between proposed UI variants), we collect aggregate, non-content signals about the review interaction:

  • that a human opened the review preview for a round, and whether the review was completed or abandoned;
  • per-round aggregate counts only: how many elements and variants were proposed, and how many per-element comments, skipped-element comments, and free-form annotations were left — never the comment text, element names, variant code, or file paths;
  • whether a round carried a round-wide comment, and whether the optional comment inspector or "reveal off-screen choices" affordances were used;
  • how long a review round took, from the first proposal to submission;
  • how many times the argent lens command was run, and how many coding-agent choices its picker offered (a count only, never the agent names).

Environment

  • Argent version, Node.js version, operating system, processor architecture;
  • whether the process runs in an interactive terminal and whether it runs in a CI environment;
  • whether Argent is used in connection with Android, iOS, tvOS (Apple TV), Android TV, VegaOS, or a Chromium-based target.

Diagnostics

  • the fact that an error occurred (event type and code only — never the error content or message);
  • when a tool call is rejected because its parameters fail validation: which parameter names failed. These names come exclusively from Argent's own published tool schemas — never the values you supplied, and never key names you typed that are not part of the schema (an unrecognised key is recorded only as the fixed marker "unrecognized_keys", not the key itself);
  • the outcome of a debugger connectivity check, recorded as one of a fixed set of coded reasons (for example: connected, Metro not running, no app attached) — never the underlying error text.

Identifiers

  • an installation identifier used to distinguish devices and to de-duplicate events. It is a one-way HMAC-SHA256 hash derived from a device identifier: the raw device identifier is never transmitted or stored and cannot be recovered from the hash. The identifier is stable across installations and reinstallations of Argent on the same device, so that reinstalling Argent does not create a new counted user. It is not linked to your name, username, or any account;
  • a random session identifier generated for each usage session.

What we never collect

To be explicit, Argent telemetry never includes:

  • tool input or argument values (for a rejected call we record only which schema-declared parameter names failed validation, as described under Diagnostics — never their values, and never key names that are not part of Argent's own schemas);
  • file paths or file names;
  • source code or its contents;
  • application data;
  • device identifiers (e.g. hardware IDs, MAC addresses, serial numbers);
  • the content or text of error messages.

Who we disclose the data to

Telemetry is transmitted using the OpenTelemetry protocol to a telemetry collector operated by Software Mansion, the controller named below. We do not share it with a third-party product-analytics provider.

Telemetry data is stored within the European Economic Area (EEA). Where we rely on an infrastructure provider (for example, cloud hosting) to operate the collector, that provider acts as our processor under a Data Processing Agreement and is engaged on the basis of EEA hosting; we keep any such list of sub-processors to a strict minimum, and the current list is available from us on request.

The controller is Software Mansion S.A. (details below). To the extent that the telemetry described here constitutes Personal Data, we process it on the basis of our Legitimate Interest (Article 6(1)(f) GDPR) in maintaining, securing, and improving Argent, consistent with the legitimate-interest grounds set out in the Policy. We have carried out a balancing assessment, taking into account the minimal and non-content nature of the data and the simple opt-out above, and concluded that this interest is not overridden by your interests or fundamental rights and freedoms. You may object to this processing at any time by opting out.

How long we keep the data

Telemetry events are retained for up to 72 months, after which they are deleted or aggregated into non-identifiable statistics.

International data transfers

Telemetry data is stored within the EEA. Should any access from outside the EEA occur (for example, for support or to operate our infrastructure), it is governed by an appropriate transfer mechanism, such as the European Commission's Standard Contractual Clauses.

Your rights

Subject to the conditions in the GDPR, you have the right of access, rectification, erasure, restriction of processing, data portability, and the right to object, as described in the Data Subject Rights section of the Policy. Because the data is not linked to a named individual or account, we may be unable to identify your specific records without additional information from you (Article 11 GDPR); the most effective way to stop processing is to opt out as described above.

To exercise these rights, contact us at legal@swmansion.com.

You also have the right to lodge a complaint with a competent supervisory authority. In Poland this is the Personal Data Protection Office (UODO), ul. Stawki 2, 00-193 Warszawa.

Controller and contact

The controller of your Personal Data is Software Mansion S.A., a joint stock company with its principal place of business at ul. Zabłocie 43b, 30-701 Kraków, Poland, entered in the register of businesses conducted by the District Court in Kraków for Kraków-Śródmieście, XI Commercial Division of the National Court Register with KRS number 0000961952, NIP 6793131302, REGON 364909814.

For any questions or requests regarding this notice, contact us at legal@swmansion.com.

Changes to this notice

We may update this notice as Argent evolves. The most current version will be available with the effective date and version shown at the top, and material changes will be announced through the usual Argent release channels.