feat: anonymous opt-out telemetry (PostHog) - #74
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an anonymous, opt-out telemetry layer backed by PostHog, wired into the CLI via Commander hooks, along with transparency commands (mex telemetry inspect/status) and supporting docs/tests.
Changes:
- Added PostHog-backed telemetry module with a whitelisted payload, opt-out checks, first-run notice, and best-effort flush.
- Hooked telemetry into the CLI lifecycle and added
mex telemetry+mex config set telemetry on|offcommands. - Added documentation (TELEMETRY.md), changelog/readme updates, and a comprehensive telemetry test suite (with Vitest env hard-disable by default).
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
vitest.config.ts |
Forces telemetry off in tests by default via MEX_TELEMETRY=0. |
test/telemetry.test.ts |
Adds unit coverage for opt-out precedence, payload whitelist, dev-repo guard, first-run notice, and read-only behaviors. |
TELEMETRY.md |
Documents data collection, opt-out paths, trust properties, and transparency commands. |
src/telemetry/index.ts |
Implements telemetry enablement checks, payload construction, PostHog client wrapper, capture/flush, and first-run notice. |
src/global-config.ts |
Adds global config + machine-id file handling and dev-repo detection. |
src/config.ts |
Adds read-only readScaffoldId() for inspect/telemetry hooks. |
src/cli.ts |
Wires telemetry into preAction/postAction, adds telemetry and config set commands, and switches to parseAsync(). |
README.md |
Adds a telemetry section linking to TELEMETRY.md and opt-out instructions. |
package.json |
Adds posthog-node dependency. |
package-lock.json |
Locks posthog-node and transitive deps. |
CHANGELOG.md |
Records the telemetry feature and related user-facing commands. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+54
to
+66
| program.hook("preAction", (_thisCommand, actionCommand) => { | ||
| try { | ||
| let scaffoldId: string | undefined; | ||
| try { | ||
| scaffoldId = readScaffoldId(findConfig().scaffoldRoot); | ||
| } catch { | ||
| // No scaffold (or not in one) — omit scaffold_id. | ||
| } | ||
| captureCommand(actionCommand.name(), scaffoldId); | ||
| } catch { | ||
| // Telemetry must never affect command behaviour. | ||
| } | ||
| }); |
Comment on lines
+205
to
+208
| const timeoutPromise = new Promise<void>((resolve) => | ||
| setTimeout(resolve, FLUSH_TIMEOUT_MS), | ||
| ); | ||
| await Promise.race([client.flush(), timeoutPromise]); |
Comment on lines
+109
to
+111
| * Generalized from `setup/index.ts:127-138` — checks the real package name | ||
| * `mex-agent` plus legacy names `promexeus` and `mex` for safety. | ||
| */ |
Comment on lines
+371
to
+377
| it("importing the module does not create files or make network calls", () => { | ||
| // The module is already imported — check that no ~/.mex/ was created | ||
| // in a fresh temp home before any explicit call | ||
| const freshHome = mkdtempSync(join(tmpdir(), "mex-import-")); | ||
| expect(existsSync(join(freshHome, ".mex"))).toBe(false); | ||
| rmSync(freshHome, { recursive: true, force: true }); | ||
| }); |
Comment on lines
+56
to
+62
| // 3. Dev-repo guard (no disk read for global config yet) | ||
| if (isDevRepo()) { | ||
| return { enabled: false, reason: "dev" }; | ||
| } | ||
|
|
||
| // 4. Global config opt-out | ||
| try { |
Comment on lines
+36
to
+41
| * Precedence (first match wins): | ||
| * 1. `DO_NOT_TRACK=1` → off | ||
| * 2. `MEX_TELEMETRY=0` → off | ||
| * 3. Dev repo / `MEX_DEV` → off | ||
| * 4. `~/.mex/config.json` `telemetry === "off"` → off | ||
| * 5. else → on |
theDakshJaitly
added a commit
that referenced
this pull request
Jun 9, 2026
- preAction no longer fires for the telemetry/config meta-commands, so `mex telemetry inspect` sends no event and never creates the machine-id file — it stays a pure audit surface even when telemetry is enabled. - flush() now clears its race timer in a finally, so a fast flush can't leave an 800ms timeout pending and delay process exit. - Remove unused `constants` import in global-config.ts. - Fix isDevRepo JSDoc: the bare `mex` package name is intentionally excluded. - Make the "no I/O at import time" test real — it now re-evaluates the module under a fresh HOME and asserts nothing is written, instead of passing vacuously. - TELEMETRY.md: drop the non-interactive auto-disable claim; CI usage is counted (no TTY gate), so the doc matches the code.
Add a thin telemetry layer that counts command usage anonymously. All PostHog-specific code lives behind src/telemetry/index.ts so the backend can be swapped in one file. - Whitelist payload only: machine_id, scaffold_id, command name, mex_version, os, node_version. No args, paths, file contents, repo names, IP, or geo (disableGeoip is set so PostHog never derives location from the IP). - Opt-out precedence: DO_NOT_TRACK=1, MEX_TELEMETRY=0, `mex config set telemetry off`, else on. Dev-repo guard hard-disables when run from a clone of mex itself, before any disk read. - machine_id is a random UUID at ~/.mex/telemetry-id (0600), created only when enabled. scaffold_id (from E1) is passed as a string only — never the identity object — so scaffold_name/origin/upstream can't leak. - Fire-and-forget: capture fires in a preAction hook (so process.exit commands like `check` on drift are still counted); flush is best-effort and bounded; a telemetry failure never blocks or changes a command's exit code. - Transparency: `mex telemetry inspect` prints the exact would-be payload without sending (and without minting the machine-id file); `mex telemetry status` shows enabled/disabled + reason; one-time first-run notice to stderr. - Tests never emit real events (vitest sets MEX_TELEMETRY=0). - Docs: TELEMETRY.md + README link + CHANGELOG entry.
- preAction no longer fires for the telemetry/config meta-commands, so `mex telemetry inspect` sends no event and never creates the machine-id file — it stays a pure audit surface even when telemetry is enabled. - flush() now clears its race timer in a finally, so a fast flush can't leave an 800ms timeout pending and delay process exit. - Remove unused `constants` import in global-config.ts. - Fix isDevRepo JSDoc: the bare `mex` package name is intentionally excluded. - Make the "no I/O at import time" test real — it now re-evaluates the module under a fresh HOME and asserts nothing is written, instead of passing vacuously. - TELEMETRY.md: drop the non-interactive auto-disable claim; CI usage is counted (no TTY gate), so the doc matches the code.
theDakshJaitly
force-pushed
the
feat/telemetry-core
branch
from
June 9, 2026 06:25
8f5ad07 to
f16bdc8
Compare
This was referenced Jun 9, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #73 (E1 — scaffold identity). Base this review against the E1 branch; once #73 merges to
main, GitHub will retarget this PR tomainautomatically. The telemetry layer needs E1'sscaffold_idas its grouping key.What
A thin, anonymous, opt-out telemetry layer that counts command usage. All PostHog-specific code lives behind
src/telemetry/index.tsso the backend can be swapped in one file.Trust properties
machine_id,scaffold_id,commandname,mex_version,os,node_version. No args, paths, file contents, repo names, IP, or location (disableGeoipis set so PostHog never derives geo from the request IP).DO_NOT_TRACK=1,MEX_TELEMETRY=0,mex config set telemetry off, and a dev-repo guard that hard-disables when run from a clone of mex itself (checked before any disk read).machine_id(random UUID at~/.mex/telemetry-id, mode 0600) is created only when enabled.scaffold_idis passed as a string only — never the identity object — soscaffold_name/origin/upstreamcannot leak.preActionhook (so commands thatprocess.exit, likecheckon drift, are still counted); flush is best-effort and bounded; all errors swallowed.mex telemetry inspectprints the exact would-be payload without sending (and without minting the machine-id file);mex telemetry statusshows state + active opt-out reason; one-time first-run notice to stderr.Tests / docs
219 tests pass, typecheck clean. The suite can never emit real events (
vitest.config.tssetsMEX_TELEMETRY=0). AddsTELEMETRY.md, a README link, and a CHANGELOG entry.Note
The embedded
phc_...key is PostHog's write-only ingestion key, documented as safe to ship in client code.