You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Archon ships as a binary via Homebrew, curl install, and (eventually) archon serve with bundled web UI per #978. We have zero visibility into how users actually use it once they install. This makes prioritization decisions ("which workflows are popular?", "which commands fail most often?", "is anyone using the Codex provider?") guesses rather than informed.
This issue proposes adding opt-in anonymous usage analytics via PostHog so we can answer those questions with data.
Why PostHog
Open-source (own deployment possible if cloud is ever a concern)
Free tier covers ~1M events/month, plenty for archon's expected scale
Reverse proxy support for users behind corporate firewalls / ad blockers
Per-event property model matches our use case (events with structured data, not just page views)
Privacy posture (the load-bearing part)
This is a developer tool. The users are technical. They will (correctly) be skeptical of telemetry. The implementation must be:
Opt-out by default — telemetry sends nothing until the user explicitly turns it on
Transparent — archon telemetry inspect shows the literal JSON of what would be sent
Easy to disable — multiple opt-out paths (CLI command, config flag, env var, industry-standard DO_NOT_TRACK=1)
Whitelist data, not blacklist — only explicitly allowed properties get sent; everything else is silently dropped
Anonymous by design — no account, no email, no GitHub username, no machine fingerprint beyond a locally-generated random UUID
Documented in plain language — a docs/telemetry.md page that lists every event and every property in plain English
If we cannot meet all six, we should not ship telemetry at all.
Architecture
Library
posthog-node — official PostHog Node.js SDK. Buffered async sender, never blocks the main thread, flushes on shutdown so events do not get lost.
Build-time API key embedding
Composes with the build-time constants refactor in #979. Add BUNDLED_POSTHOG_KEY and BUNDLED_POSTHOG_HOST to packages/paths/src/bundled-build.ts:
exportconstBUNDLED_IS_BINARY=false;exportconstBUNDLED_VERSION='dev';exportconstBUNDLED_GIT_COMMIT='unknown';exportconstBUNDLED_POSTHOG_KEY='';// empty in dev = telemetry impossibleexportconstBUNDLED_POSTHOG_HOST='https://eu.posthog.com';
The build script writes the real key from a GitHub Actions secret (POSTHOG_PUBLIC_KEY) when building release binaries:
Critical: dev mode has an empty key. Telemetry can never be sent from a dev clone, even if a developer accidentally enables it. This protects internal usage from polluting production data.
The PostHog public/ingest key (phc_...) is safe to embed in client code — it is write-only ingestion, not the read API key. PostHog explicitly documents this pattern.
Anonymous identity
A UUID v4 generated once per machine and stored at ~/.archon/telemetry-id with mode 0600. Generated only when the user opts in — if telemetry is off, the file does not exist.
DO_NOT_TRACK=1 is the W3C / industry-standard universal opt-out signal. Many privacy-conscious users set this in their shell rc to opt out of all telemetry from all tools. Honoring it is table stakes.
Whitelist approach. Only the explicitly allowed properties get sent. Everything else is silently dropped.
Category
Examples
Why excluded
File paths
cwd, --cwd /home/me/secret-repo
Identifies user's filesystem layout
File names
workflow file names, command file names
Could be sensitive
User-defined workflow names
my-internal-deploy
Could leak company project names
Branch names
feature/customer-x-integration
Could leak feature plans
Commit hashes (other than archon's own)
repo HEAD, PR head SHA
Same
Prompts / messages / chat content
Anything the user typed
Privacy disaster
Error messages (only error classes)
"Failed to connect to internal-vpn.acme.com"
Could leak hostnames, internal infra
Env var values
All of them
Could leak secrets
GitHub repo names
acme/proprietary-thing
Privacy
Owner names
acme-corp
Privacy
Workflow definitions
YAML content
Could leak company process
AI tokens, API keys, OAuth tokens
All
Catastrophic if leaked
Sanitization helpers
User-defined workflow names get bucketed as 'user-defined' so we can see usage volume without leaking names:
constALLOWED_WORKFLOW_NAMES=newSet(['archon-assist','archon-fix-github-issue','archon-comprehensive-pr-review',// ... all bundled defaults from packages/workflows/src/defaults/]);exportfunctionsanitizeWorkflowName(name: string): string{returnALLOWED_WORKFLOW_NAMES.has(name) ? name : 'user-defined';}
Same approach for command names — bucket non-bundled into 'user-defined'.
// packages/telemetry/src/client.tsimport{PostHog}from'posthog-node';import{BUNDLED_POSTHOG_KEY,BUNDLED_POSTHOG_HOST,BUNDLED_VERSION,BUNDLED_GIT_COMMIT,BUNDLED_IS_BINARY}from'@archon/paths';import{getOrCreateAnonymousId}from'./identity';import{isTelemetryEnabled}from'./consent';letclient: PostHog|null=null;functiongetClient(): PostHog|null{if(!isTelemetryEnabled())returnnull;if(!BUNDLED_POSTHOG_KEY)returnnull;if(client)returnclient;client=newPostHog(BUNDLED_POSTHOG_KEY,{host: BUNDLED_POSTHOG_HOST,flushAt: 20,// batch up to 20 eventsflushInterval: 10000,// or every 10srequestTimeout: 5000,// never block CLI shutdown for more than 5s});returnclient;}exportfunctiontrackEvent(event: string,properties: Record<string,unknown>={}): void{constph=getClient();if(!ph)return;try{ph.capture({distinctId: getOrCreateAnonymousId(),
event,properties: {
...buildBaseProperties(),
...properties,},});}catch{// Telemetry must NEVER throw or block. Silently swallow errors.}}functionbuildBaseProperties(): Record<string,unknown>{return{archon_version: BUNDLED_VERSION,archon_git_commit: BUNDLED_GIT_COMMIT,archon_is_binary: BUNDLED_IS_BINARY,platform: process.platform,arch: process.arch,bun_version: process.versions.bun??'unknown',node_version: process.versions.node??'unknown',};}exportasyncfunctionshutdownTelemetry(): Promise<void>{if(!client)return;try{awaitclient.shutdown();}catch{// never throw on shutdown}client=null;}export{isTelemetryEnabled};
archon telemetry on # opt in (writes config + creates anonymous ID)
archon telemetry off # opt out (writes config, leaves anonymous ID alone)
archon telemetry status # show current state, source of decision, anonymous ID
archon telemetry inspect # show last 10 events that WOULD be sent (in JSON, never actually sends)
archon telemetry clear # delete anonymous ID file (resets identity, doesn't change opt-in state)
archon telemetry policy # print the link to docs/telemetry.md
status example output:
Telemetry: ENABLED
Source: ~/.archon/config.yaml (telemetry.enabled = true)
Anonymous ID: 7f3b2a8c-... (created 2026-04-08)
PostHog host: https://eu.posthog.com
PostHog key: phc_xxx... (embedded at build time)
Last event: cli.command_completed (3 minutes ago)
To disable: archon telemetry off
To inspect what is being sent: archon telemetry inspect
Full policy: https://github.com/coleam00/Archon/blob/main/docs/telemetry.md
When a user runs any archon command for the first time after install (detected by absence of ~/.archon/.first-run-completed), print this notice once:
─────────────────────────────────────────────────────────────────
Archon collects no usage data by default.
If you'd like to share anonymous usage events to help improve archon:
archon telemetry on
Review what would be sent at any time:
archon telemetry inspect
Full telemetry policy:
https://github.com/coleam00/Archon/blob/main/docs/telemetry.md
─────────────────────────────────────────────────────────────────
Then write ~/.archon/.first-run-completed so the notice never repeats.
Do NOT prompt interactively — that breaks scripts and CI. The notice is informational only; users have to explicitly run telemetry on to enable.
Reverse proxy (recommended for production deployment)
PostHog cloud lives at app.posthog.com / eu.posthog.com. These are blocked by:
Many corporate firewalls
Ad blockers (uBlock Origin, Pi-hole)
Privacy-focused DNS resolvers (NextDNS, AdGuard)
DNS-based VPN profiles
If we want telemetry to actually arrive from real users, set up a reverse proxy:
Cloudflare Worker (recommended): ~10 lines of JS, free tier, custom subdomain like telemetry.archon.dev
Self-hosted nginx/Caddy: more control, more maintenance
Vercel edge function: if you already have Vercel infrastructure
Composes naturally. This issue extends bundled-build.ts with two more constants (BUNDLED_POSTHOG_KEY, BUNDLED_POSTHOG_HOST). Both issues touch the same file. Recommended sequence: land #979 first, then this on top.
Server events (API requests, workflow runs from web/Slack/etc.)
Server running somewhere — works in dev clone today, would also work in #978's binary
Web UI events (page views, button clicks, feature usage)
posthog-js in packages/web — completely independent of how the server is shipped
You could instrument all three TODAY without #978, by adding posthog-js to the dev-clone web UI. Server telemetry already works wherever the server runs. Only "binary users get web UI telemetry" is gated on #978.
Recommendation: ship CLI telemetry first (this issue, narrow scope). Add server telemetry independently when there is bandwidth. Add web UI telemetry as a third orthogonal piece. Do not gate this issue on #978 — that delays data collection by weeks.
CI / release workflow
The release workflow (.github/workflows/release.yml) needs:
A new POSTHOG_PUBLIC_KEY secret in the repo (set by maintainer)
Optionally POSTHOG_HOST if using a reverse proxy
The build script wrapper to pass them as env vars
Both are 2-line additions to the workflow.
Phased plan
Phase 1 — Infrastructure (1 day)
Add posthog-node dependency to a new packages/telemetry/ package
EU vs US PostHog hosting — recommend EU for GDPR alignment. Confirm.
Reverse proxy backend — Cloudflare Worker is cheapest and fastest. Do we want it?
Should telemetry-on be the default? Strongly recommend NO. Opt-in is the only ethical default for a developer tool.
Should we capture telemetry from archon-stable (released binary) AND archon (bun link dev binary)? Recommend NO for dev — empty key in dev mode prevents this anyway.
Data retention — PostHog default is 7 years. Recommend setting to 1 year max.
Web UI telemetry via posthog-js? Recommend YES, separate sub-issue. Independent of CLI.
Out of scope (deferred to follow-ups)
Server-side telemetry from packages/server/ — separate sub-issue once consensus on this one
Web UI telemetry via posthog-js — separate sub-issue, independent of CLI binary
Custom dashboards in PostHog — operational concern, not a feature
Automatic anomaly detection — too speculative
A/B testing infrastructure — different feature entirely
Funnel analysis on user journeys — data first, analysis second
Success criteria
Default install of the next archon release sends ZERO telemetry events
After archon telemetry on, the next CLI invocation shows up in PostHog within 30 seconds
archon telemetry inspect output matches what PostHog actually receives (verified by spot-checking 10 events)
Setting DO_NOT_TRACK=1 in the shell rc disables all telemetry without any other config
A user who never runs archon telemetry on has no ~/.archon/telemetry-id file (no anonymous identity created)
The privacy whitelist is enforced — adding a new event property fails the lint/build if the property is not on the allowed list (stretch goal: make this a TypeScript type constraint)
The reverse proxy is documented and the binary points at it by default
Context
Archon ships as a binary via Homebrew, curl install, and (eventually)
archon servewith bundled web UI per #978. We have zero visibility into how users actually use it once they install. This makes prioritization decisions ("which workflows are popular?", "which commands fail most often?", "is anyone using the Codex provider?") guesses rather than informed.This issue proposes adding opt-in anonymous usage analytics via PostHog so we can answer those questions with data.
Why PostHog
posthog-node) — small, batched, async, handles offline gracefullyPrivacy posture (the load-bearing part)
This is a developer tool. The users are technical. They will (correctly) be skeptical of telemetry. The implementation must be:
archon telemetry inspectshows the literal JSON of what would be sentDO_NOT_TRACK=1)docs/telemetry.mdpage that lists every event and every property in plain EnglishIf we cannot meet all six, we should not ship telemetry at all.
Architecture
Library
posthog-node— official PostHog Node.js SDK. Buffered async sender, never blocks the main thread, flushes on shutdown so events do not get lost.Build-time API key embedding
Composes with the build-time constants refactor in #979. Add
BUNDLED_POSTHOG_KEYandBUNDLED_POSTHOG_HOSTtopackages/paths/src/bundled-build.ts:The build script writes the real key from a GitHub Actions secret (
POSTHOG_PUBLIC_KEY) when building release binaries:Critical: dev mode has an empty key. Telemetry can never be sent from a dev clone, even if a developer accidentally enables it. This protects internal usage from polluting production data.
The PostHog public/ingest key (
phc_...) is safe to embed in client code — it is write-only ingestion, not the read API key. PostHog explicitly documents this pattern.Anonymous identity
A UUID v4 generated once per machine and stored at
~/.archon/telemetry-idwith mode0600. Generated only when the user opts in — if telemetry is off, the file does not exist.The ID is not tied to anything personal. Wiping the file resets your identity (
archon telemetry clear).Consent resolution
Multiple opt-in/out signals, resolved in priority order:
Default order:
DO_NOT_TRACK> env var > config file > default off.DO_NOT_TRACK=1is the W3C / industry-standard universal opt-out signal. Many privacy-conscious users set this in their shell rc to opt out of all telemetry from all tools. Honoring it is table stakes.What to capture (events)
binary.startupcli.command_invokedcommand,subcommand(no args/values)cli.command_completedcommand,subcommand,exit_code,duration_msworkflow.run_startedworkflow_name(sanitized),node_count,provider,is_cli_invokedworkflow.run_completedworkflow_name,duration_ms,node_count,nodes_completedworkflow.run_failedworkflow_name,error_class,failed_node_type,duration_msworkflow.node_failedworkflow_name,node_type,error_classification(transient/fatal/credit_exhausted),providererror.uncaughterror_class,where(function name only, no path)setup.completedinstall_method(homebrew/curl/source),default_assistantCommon properties on every event (auto-included)
What NEVER gets sent
Whitelist approach. Only the explicitly allowed properties get sent. Everything else is silently dropped.
cwd,--cwd /home/me/secret-repomy-internal-deployfeature/customer-x-integration"Failed to connect to internal-vpn.acme.com"acme/proprietary-thingacme-corpSanitization helpers
User-defined workflow names get bucketed as
'user-defined'so we can see usage volume without leaking names:Same approach for command names — bucket non-bundled into
'user-defined'.Implementation skeleton
New package:
packages/telemetry/Public API
Client wrapper
Wiring into the CLI
Then individual command handlers add their own
trackEventcalls. Example:User-facing CLI commands
Three new commands under
archon telemetry:statusexample output:inspectexample output:{ "events": [ { "event": "cli.command_invoked", "distinctId": "7f3b2a8c-...", "properties": { "command": "workflow", "subcommand": "run", "archon_version": "0.2.13", "archon_git_commit": "abc1234", "archon_is_binary": true, "platform": "darwin", "arch": "arm64", "bun_version": "1.3.11", "node_version": "20.x" } }, ... ] }First-run notice
When a user runs any
archoncommand for the first time after install (detected by absence of~/.archon/.first-run-completed), print this notice once:Then write
~/.archon/.first-run-completedso the notice never repeats.Do NOT prompt interactively — that breaks scripts and CI. The notice is informational only; users have to explicitly run
telemetry onto enable.Reverse proxy (recommended for production deployment)
PostHog cloud lives at
app.posthog.com/eu.posthog.com. These are blocked by:If we want telemetry to actually arrive from real users, set up a reverse proxy:
telemetry.archon.devPostHog officially documents this pattern: https://posthog.com/docs/advanced/proxy
The
BUNDLED_POSTHOG_HOSTconstant inbundled-build.tsgets set to the proxy URL at release time:Documentation:
docs/telemetry.mdA plain-language page covering:
events.ts?)This page is the load-bearing trust artifact. Skimping on it undermines the whole feature.
Coupling with other issues
#979 (build-time constants)
Composes naturally. This issue extends
bundled-build.tswith two more constants (BUNDLED_POSTHOG_KEY,BUNDLED_POSTHOG_HOST). Both issues touch the same file. Recommended sequence: land #979 first, then this on top.#978 (web UI distribution via Option E)
Not a hard blocker. Three orthogonal layers:
workflow run,binary startup, errors)posthog-jsinpackages/web— completely independent of how the server is shippedYou could instrument all three TODAY without #978, by adding
posthog-jsto the dev-clone web UI. Server telemetry already works wherever the server runs. Only "binary users get web UI telemetry" is gated on #978.Recommendation: ship CLI telemetry first (this issue, narrow scope). Add server telemetry independently when there is bandwidth. Add web UI telemetry as a third orthogonal piece. Do not gate this issue on #978 — that delays data collection by weeks.
CI / release workflow
The release workflow (
.github/workflows/release.yml) needs:POSTHOG_PUBLIC_KEYsecret in the repo (set by maintainer)POSTHOG_HOSTif using a reverse proxyBoth are 2-line additions to the workflow.
Phased plan
Phase 1 — Infrastructure (1 day)
posthog-nodedependency to a newpackages/telemetry/packageconsent.ts,identity.ts,client.ts,sanitize.tsindex.tsPhase 2 — CLI integration (0.5 day)
archon telemetry on/off/status/inspect/clear/policycommandstrackEventcalls into CLI startup, command handlers, error catchersbinary.startup,cli.command_invoked,cli.command_completed,error.uncaughteventsPhase 3 — Workflow events (0.5 day)
packages/workflows/src/executor.tsanddag-executor.tsworkflow.run_started,workflow.run_completed,workflow.run_failed,workflow.node_failedPhase 4 — Build script + CI (0.5 day)
scripts/build-binaries.shto write PostHog constants from env vars.github/workflows/release.ymlto passPOSTHOG_PUBLIC_KEYfrom secretsPhase 5 — Reverse proxy + docs (0.5 day)
docs/telemetry.mdPhase 6 — Validation (0.5 day)
DO_NOT_TRACK=1, verify no events arriveinspectcommand shows the same data PostHog receivesTotal: ~3.5 days of focused work.
Files to change
packages/paths/src/bundled-build.tsBUNDLED_POSTHOG_KEY,BUNDLED_POSTHOG_HOST)packages/telemetry/packages/cli/src/commands/telemetry.tspackages/cli/src/commands/setup.tspackages/cli/src/cli.tsbinary.startup, shutdown handlerspackages/cli/src/commands/workflow.tspackages/workflows/src/executor.tspackages/workflows/src/dag-executor.tsscripts/build-binaries.sh.github/workflows/release.ymldocs/telemetry.mdpackages/docs-web/src/content/docs/reference/telemetry.mdREADME.mdCHANGELOG.mdOpen questions for discussion
archon-stable(released binary) ANDarchon(bun link dev binary)? Recommend NO for dev — empty key in dev mode prevents this anyway.packages/server/src/? Same@archon/telemetrypackage can be imported. Recommend YES once the server is in scope (depends on feat(distribution): one-command web UI install via lazy-fetch from release tarball #978 for binary; can ship for dev clone immediately).posthog-js? Recommend YES, separate sub-issue. Independent of CLI.Out of scope (deferred to follow-ups)
packages/server/— separate sub-issue once consensus on this oneposthog-js— separate sub-issue, independent of CLI binarySuccess criteria
archon telemetry on, the next CLI invocation shows up in PostHog within 30 secondsarchon telemetry inspectoutput matches what PostHog actually receives (verified by spot-checking 10 events)DO_NOT_TRACK=1in the shell rc disables all telemetry without any other configarchon telemetry onhas no~/.archon/telemetry-idfile (no anonymous identity created)Related
BUNDLED_VERSIONprecedent for build-time constants inpackages/paths/src/bundled-build.ts(after fix(build): use build-time constants for binary detection and pretty stream logger (replaces #962/#963) #979)