Anonymous posthog telemetry
Skill kjuhwa/skills-hub/skills/observability/anonymous-posthog-telemetry
Ship anonymous PostHog telemetry from an OSS CLI using an embedded write-only key, a silentFetch wrapper that masks SDK stderr noise, DO_NOT_TRACK / per-tool opt-out, and `$process_person_profile: false` to stay in PostHog's anonymous tier.From its SKILL.md
npx -y skills add kjuhwa/skills-hub --skill anonymous-posthog-telemetryAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.4 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
Anonymous, Opt-Out, No-Noise PostHog Telemetry for OSS CLIs
When to use
- You ship an open-source CLI and want a single metric (e.g. "this command was invoked") to measure usage, not user behavior.
- You cannot require users to provide a PostHog key, but you don't want to collect PII or build person profiles.
- Network failures (offline, firewall, DNS broken, rate-limited) must not spam the user's terminal with stderr noise — telemetry is strictly fire-and-forget.
Steps
-
Embed a write-only PostHog project key (
phc_*) as a default. These keys can only write events, never read data — safe to ship in source. Allow override viaPOSTHOG_API_KEYenv var (for self-hosted PostHog). -
Respect these opt-outs (any one disables telemetry):
<TOOL>_TELEMETRY_DISABLED=1(your tool-specific var)DO_NOT_TRACK=1(de facto standard across OSS tools)POSTHOG_API_KEYunset and no embedded default
Check them in an exported
isTelemetryDisabled()so callers / tests can short-circuit before spinning up the client. -
Generate and persist a stable anonymous install UUID. Read
~/.<tool>/telemetry-idif present, elserandomUUID()and write. On read/write failure, use an ephemeral UUID for this session (telemetry still works, just not correlated across runs). Never prompt, never fail. -
Stay in PostHog's anonymous tier. On every
capture(), include$process_person_profile: falsein the properties. This disables person profile creation — events count for metrics but don't create a "user" entity. See PostHog docs on anonymous events. -
Wrap the SDK's
fetchwith asilentFetchthat masks all errors as 200 responses. PostHog-node's internallogFlushErrorwrites to stderr viaconsole.erroron any network error, bypassing your logger configuration. Intercept first:const FAKE_OK = { status: 200, text: () => Promise.resolve('{}'), json: () => Promise.resolve({}), headers: { get: () => null } }; async function silentFetch(url, options) { try { const res = await fetch(url, options); if (res.status < 200 || res.status >= 400) return FAKE_OK; return res; } catch { return FAKE_OK; } }Log the original error at debug level so it's discoverable but not user-visible. Pass
silentFetchvia the PostHog constructor'sfetchoption. -
Hook the client-level error channel too (
client.on('error', …)) — future SDK versions may route errors there instead of (or in addition to)console.error. Log at debug. -
Set
disableGeoip: true,flushAt: 20,flushInterval: 10000. Geo-IP is enrichment you don't need; flushAt/Interval keep capture latency low while batching. -
Make every
capture*function fire-and-forget.export function captureWorkflowInvoked(props) { if (isTelemetryDisabled()) return; void (async () => { try { const c = await getClient(); c?.capture({...}); } catch {} })(); }Synchronous return, async IIFE inside, no user-visible failure path.
-
Implement
shutdownTelemetry()for process exit (SIGTERM, CLI completion) thatawait client.shutdown()to flush pending batches, with a try/catch and internal state reset for test reuse. -
Trim potentially-unbounded fields (workflow descriptions, user messages). Archon caps
workflow_descriptionat 500 chars.
Counter / Caveats
- Don't capture anything that could be PII: command arguments, file paths with usernames, chat messages, repo names. Archon captures only
workflow_name, optionalworkflow_description(trimmed, authored by the tool author not the user),platform,archon_version. - Think hard about per-command telemetry: aggregating is fine, but a user-typed workflow name might contain unintended identifiers. If in doubt, bucket into a fixed enum.
- Document telemetry prominently in your README — opt-in/opt-out transparency is non-negotiable for OSS.
- The
phc_*key limits write-volume server-side; PostHog itself enforces rate-limits. Don't worry about "abuse" of the key beyond normal operation.
Evidence
packages/paths/src/telemetry.ts(247 lines): full implementation.- Embedded key + opt-outs: lines 42-75.
getOrCreateTelemetryIdwith read/write fallbacks: lines 87-106.silentFetch+FAKE_OK_RESPONSE: lines 137-159.- PostHog init with
disableGeoip,flushAt: 20,flushInterval: 10000, customfetch: lines 161-183. client.on('error', …)defensive hook: lines 175-177.$process_person_profile: falsein capture: line 207.- Fire-and-forget
captureWorkflowInvoked: lines 196-218. shutdownTelemetry: lines 225-237.- 500-char description trim:
DESCRIPTION_MAX_LENGTHconstant at line 50.
- Commit SHA: d89bc767d291f52687beea91c9fcf155459be0d9.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.