Anonymous posthog telemetry
Skill kjuhwa/skills-hub/skills/observability/anonymous-posthog-telemetry
Self-correcting knowledge corpus for Claude Code — 9 stable shape clusters, bias-correction pipeline baked into contribution flow. 47 papers, 45 techniques, 1.1k skills.
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.
What its author says it does
Copied from the file, not written here
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.
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.