Skip to content

πŸ€– feat: add Usage Telemetry toggle to Settings β†’ General - #3850

Open
asm wants to merge 12 commits into
coder:mainfrom
asm:telemetry-settings-toggle
Open

πŸ€– feat: add Usage Telemetry toggle to Settings β†’ General#3850
asm wants to merge 12 commits into
coder:mainfrom
asm:telemetry-settings-toggle

Conversation

@asm

@asm asm commented Aug 14, 2026

Copy link
Copy Markdown

Summary

Adds a Usage Telemetry toggle to Settings β†’ General (under a new Privacy group) so telemetry can be disabled (and re-enabled) from the UI. The choice persists as telemetryEnabled in ~/.mux/config.json and applies immediately β€” disabling shuts the PostHog client down mid-session, re-enabling re-runs the full enablement check. MUX_DISABLE_TELEMETRY=1 remains a hard override that wins over the toggle; when the environment forces telemetry off, the switch renders disabled with a note saying so instead of pretending to control anything.

Background

Telemetry originally had a client-side opt-out (referenced in #905 when reporting moved to the backend), but that surface disappeared along the way, leaving the environment variable as the only user-facing switch β€” which is hard to apply to a Dock-launched macOS app (GUI apps don't inherit shell profiles). A Settings toggle is the conventional surface for this in developer tools; docs continue to point at payload.ts for transparency about what is sent.

Implementation

  • telemetryEnabled?: boolean config field (absent/true = enabled, false = disabled), stored sparsely: re-enabling deletes the key.
  • shouldEnableTelemetry gains a disabledByConfig input; TelemetryService receives an isDisabledByConfig callback from the service container and consults it during initialize() and per capture() β€” the per-event re-check keeps API-server processes honest even if a toggle apply hasn't reached them.
  • New config.updateTelemetryEnabled route persists the choice and calls telemetryService.setConfigEnabled() for the live apply. Applies are serialized (a promise chain) and initialize() is re-entrant-safe, so rapid toggling can't interleave PostHog shutdown/init and strand a live client after an opt-out; shutdown() nulls the client before awaiting the flush so nothing can capture into a draining client.
  • isExplicitlyDisabled() now includes the config opt-out, so features gated on explicit opt-out (e.g. link sharing) treat the Settings toggle the same as MUX_DISABLE_TELEMETRY=1.
  • getConfig exposes telemetryDisabledByEnv; the Settings row renders the switch hard-disabled with an explanatory note when the environment override is active.
  • Failed writes cannot leave the switch lying: Config.saveConfig swallows disk errors, so the route re-reads the persisted value after editConfig and fails loudly (before touching the live client) when the write didn't land. On the frontend, each toggle records an intent id β€” a superseded request's failure no-ops, and the latest intent's failure reloads the backend truth; if that truth is unreachable too, the switch renders ON (indeterminate state must never read "off" while collection may continue) until a successful config load reconciles it. Rapid toggling can't be clobbered by an early failure.
  • Settings row mirrors the existing API Debug Logs toggle patterns (load nonce, serialized update chain), with a "What is collected" link to the telemetry docs. Docs updated to describe the toggle, the env-disabled UI state, and that the env var must be exactly 1.

Review-round hardening

Eleven Codex review rounds tightened the privacy edges (all threads resolved):

  • Fail-closed reads: an unreadable or unparseable config.json β€” including an inaccessible ~/.mux that existsSync would mask as "missing" β€” reports disabled; only a genuine ENOENT (fresh install) means enabled. The RPC's persistence verification uses a strict read whose failure fails the request rather than masquerading as a confirmed opt-out.
  • Lifecycle races: toggle applies serialize with a re-entrant initialize(); shutdown() nulls before flushing; capture() re-checks the config per event and lazily re-initializes (rate-limited) when another process re-enables telemetry.
  • Switch honesty: indeterminate backend truth renders ON; superseded writes and superseded API clients can't clobber confirmed state; deferred config notifications replay through the current client once writes settle; the subscription re-syncs on connect to close the read-before-subscribe gap; the switch disables without a usable API. Cross-process display sync via config.json watching is documented as a follow-up (Config-level infrastructure).

Validation

  • Unit tests: config-opt-out disables enablement; env hard-off wins over config-enabled; isExplicitlyDisabled() reflects the config opt-out; telemetryEnabled round-trips the saveConfig whitelist (including clearing back to default).
  • UI tests: loading a persisted opt-out and persisting a re-enable; the env-disabled switch renders disabled/unchecked and ignores clicks; a failed write reverts the optimistic state.
  • Verified in Storybook: toggle renders under Privacy, flips both ways against the stateful mock with a clean console.

Risks

Low. The enablement change is additive (one new early-return input); with the field absent, behavior is byte-identical to today. The live-apply path reuses the existing shutdown()/initialize() lifecycle, now serialized against concurrent applies. Worst case on a config read failure inside the callback is telemetry staying in its startup state.


πŸ€– Generated with Claude Code

@asm
asm marked this pull request as draft August 14, 2026 00:21
@asm
asm marked this pull request as ready for review August 14, 2026 03:20
@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b20a688ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx Outdated
Comment thread src/node/orpc/router.ts
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Both findings addressed in 9610baa:

  • Superseded-toggle rollback: each toggle now records a monotonically increasing intent id. A failure belonging to a superseded request no-ops (a later write carries the newest choice and its own handling); a failure of the latest intent reloads the backend truth via getConfig instead of blind-flipping. Covered by a rapid onβ†’offβ†’on test with a deferred first request that fails after later intents are queued.
  • Persistence verification: saveConfig swallows write errors, so updateTelemetryEnabled now re-reads the config from disk after editConfig and throws before touching the live client when the write didn't land β€” the UI reverts instead of showing "off" while the preference silently un-applies on next launch. Covered by a router test that makes the config dir read-only.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9610baa072

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx Outdated
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Round-2 finding addressed in 47d8ad0: when the reconciling getConfig fails too (backend truth unknown), the switch now always renders ON instead of !checked β€” indeterminate state must never read "off" while collection may continue, and the next successful config load reconciles the real value. Covered by a test where both the write and the reload fail on a disable attempt.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47d8ad024f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx Outdated
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Round-3 finding addressed in c5e3eae: the switch renders disabled while api is null (browser-mode outage), and the handler refuses to flip optimistically before a deliverable write β€” covering the race where the connection drops between render and click. A disabled-state test asserts the click leaves the conservative ON state untouched.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c5e3eae115

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/config.ts Outdated
Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Both round-4 findings addressed in e54b346:

  • Fail closed on unreadable config: isTelemetryDisabledByConfig() now reads with throwOnError and reports disabled when an existing config.json cannot be read or parsed β€” corrupted state can no longer silently override an opt-out at startup or per capture(). A missing file (fresh install) still reports enabled, and callers stay non-fatal. Test writes malformed JSON and asserts disabled.
  • Cross-client sync: GeneralSection consumes the config.onConfigChanged stream (same pattern as UserPreferencesContext) and refreshes the telemetry fields on notifications, guarded by the load nonce plus a pending-writes counter so in-flight local writes reconcile through their own settle path instead of racing the refresh. Test drives a mock notification stream and asserts the switch follows an external opt-out.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e54b34668d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Round-5 finding addressed in a9819ad: notifications arriving while local writes are in flight are no longer dropped β€” they set a missed-notification flag that replays the backend refresh when the pending-writes counter drains, so an external change landing during the write window (including our own write's early notification) always reconciles. Covered by a test where another client's enable arrives mid-flight during a local opt-out and the switch ends ON.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9819ad28a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/orpc/router.ts Outdated
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Round-6 finding addressed in 34006f2: the persistence verification now re-reads with throwOnError β€” a read failure fails the RPC with its own error instead of masquerading as a confirmed opt-out, and the fail-closed read stays confined to enablement checks (isTelemetryDisabledByConfig) where reporting disabled on unreadable state is the safe direction. New router test locks config.json unreadable and the dir unwritable and asserts the RPC rejects without touching the live client.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34006f2930

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/config.ts
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Round-7 finding addressed in 893cd6b: isTelemetryDisabledByConfig() now stats the config file explicitly instead of trusting existsSync() (which reports EACCES traversal failures as "missing") β€” only a genuine ENOENT means fresh-install-enabled; any other stat/read/parse failure fails closed without crashing startup. Test makes the config directory unreachable (chmod 000) and asserts disabled.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 893cd6b2fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Round-8 finding addressed in e6aea88: the subscription now triggers a re-sync as soon as the iterator is connected, so a change landing between the initial snapshot and establishment can no longer strand a stale switch. Covered by a test that holds the subscription unestablished while another client opts out and asserts the switch syncs on connect (no event ever pushed for the missed change).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6aea88e97

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/telemetryService.ts Outdated
Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Both round-9 findings addressed in 49a35c2:

  • Cross-process re-enable: capture() now kicks a lazy, serialized initialize() when the shared config says enabled but this process's client is null (started while opted-out) β€” rate-limited to one attempt per 30s since the other enablement gates may legitimately keep it null. The process converges without a restart, in both directions now.
  • Superseded API client: an API replacement bumps the telemetry intent counter, so a late rejection from the old client fails the intent guard instead of reconciling against state the new client has since confirmed; the config subscription re-establishes on the new client and re-syncs on connect.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49a35c27c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx
@asm
asm force-pushed the telemetry-settings-toggle branch from 49a35c2 to bd10767 Compare August 14, 2026 17:57
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Round-10 finding addressed (branch also rebased onto latest main, clean replay): deferred-notification replays now go through a ref the api-change effect keeps pointed at the CURRENT client generation, so a write settling after an API replacement can no longer consume the notification against the disconnected client. Covered by a test choreographing write-pending β†’ API swap β†’ late old-client failure, asserting the switch syncs through the replacement client.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting β€œ@codex review”.

Unknown error
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd107679e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/GeneralSection.tsx
@asm

asm commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Round-12 finding β€” cross-process config.json change detection β€” descoped as a follow-up, with rationale:

  • The privacy-critical behavior already converges across processes without a watcher: capture() re-checks the persisted opt-out per event (disable propagates), fails closed on unreadable state, and lazily re-initializes when the shared config re-enables (enable propagates). What remains is a display-only lag in a second process's Settings pane, which corrects on the pane's next mount or any of that process's own config activity.
  • Watching config.json is Config-level infrastructure, not a toggle-PR bolt-on: write-file-atomic replaces the inode (so it needs directory watching), plus debouncing, self-write suppression to avoid notification loops, and platform-specific watcher semantics β€” machinery every config consumer would inherit. That deserves its own reviewed change; happy to build it as a follow-up if maintainers want it.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: bd107679e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

asm and others added 12 commits August 14, 2026 12:55
telemetryEnabled config field consulted by the telemetry service (env var
MUX_DISABLE_TELEMETRY remains a hard override); toggling applies live by
shutting down or re-initializing the PostHog client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Serialize setConfigEnabled applies and make initialize() re-entrant
  so rapid Settings toggles can't interleave PostHog shutdown/init and
  strand a live client after the user opted out.
- Null the client before awaiting shutdown so no event can be captured
  into a flushing client, and re-check the config opt-out per capture()
  for API-server callers that bypass the toggle route.
- isExplicitlyDisabled() now includes the config opt-out so features
  gated on explicit opt-out (e.g. link sharing) treat the Settings
  toggle the same as MUX_DISABLE_TELEMETRY=1.
- Expose telemetryDisabledByEnv via getConfig and render the switch
  hard-disabled with an explanatory note when the environment override
  is active, instead of pretending the toggle controls anything.
- Revert the optimistic switch state when persisting the change fails β€”
  a privacy control must not read "off" while collection continues.
- Move the toggle into its own Privacy group and document that the env
  var must be exactly "1".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex review round 1:

- updateTelemetryEnabled now re-reads the config from disk after
  editConfig and fails loudly on mismatch before touching the live
  client: saveConfig swallows write errors (full disk, unwritable
  config.json), so the route could report success for a privacy opt-out
  that silently reverts on next launch. Router test proves it by
  making the config dir read-only.
- The Settings switch tags each toggle with a monotonically increasing
  intent id: a superseded request's failure no longer blind-flips the
  switch (clobbering the user's latest choice mid rapid-toggle), and
  the latest intent's failure reloads the backend truth instead of
  guessing.
- Fix router.test.ts config-route tests broken by the earlier
  telemetryDisabledByEnv addition (partial ORPCContext now stubs
  telemetryService).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex review round 2: when a toggle write fails and the reconciling
getConfig also fails (connection dropped after the request may have
persisted and applied), the fallback rendered !checked β€” "off" after a
failed enable while telemetry may actually be collecting. Indeterminate
backend truth now always renders ON: showing "off" while collection may
continue is the one lie a privacy toggle can't tell. The next
successful config load reconciles the real value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex review round 3: with a browser-mode outage (api: null, settings
still mounted), clicking the switch flipped it optimistically and
returned without issuing a write β€” rendering OFF while the backend may
keep collecting, and silently discarding the intent. The switch now
renders disabled without a usable API, and the handler refuses to flip
before a deliverable write (covering the drop between render and
click).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… clients

Codex review round 4:

- isTelemetryDisabledByConfig() read through loadConfigOrDefault(),
  which swallows read/parse errors and returns defaults β€” a corrupted
  config.json silently re-enabled telemetry for an opted-out user at
  startup and per capture(). It now reads with throwOnError and fails
  CLOSED: an unreadable existing file reports disabled, while a missing
  file (fresh install) stays enabled and callers remain non-fatal.
- GeneralSection consumes the config.onConfigChanged stream so a second
  window/tab tracks telemetry changes made elsewhere instead of showing
  a stale switch while collection state already changed. Refreshes are
  guarded by the load nonce plus a pending-writes counter so our own
  in-flight writes reconcile through their own settle path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ites

Codex review round 5: the pending-writes guard dropped notifications
outright, and enqueueConfigEdit emits onConfigChanged before the RPC
resolves β€” so even our own final write's notification arrives while the
counter is positive, and an external change landing during the write
window was lost forever (switch stuck OFF while another client enabled
collection). Deferred notifications now set a flag that replays the
backend refresh when the write queue drains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex review round 6: the verification step reused
isTelemetryDisabledByConfig(), whose fail-closed read (unreadable file
=> disabled) is right for enablement checks but let a failed disable
write plus a failed read masquerade as a confirmed opt-out β€” the RPC
reported success for a preference that resumes collecting on restart.
The route now re-reads with throwOnError and a read failure fails the
RPC with a distinct error, before touching the live client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex review round 7: loadConfigOrDefault guards its read with
existsSync(), which reports EACCES traversal failures as "missing" β€”
so an opted-out user whose ~/.mux becomes unreachable read as
enabled-by-default despite the strict-read fix. isTelemetryDisabledByConfig
now stats the file explicitly: only a genuine ENOENT (fresh install)
means enabled; every other stat/read/parse failure fails closed,
without crashing startup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex review round 8: a config change landing between the initial
getConfig snapshot and the onConfigChanged subscription's establishment
had no listener β€” the switch stayed stale until the next unrelated
edit. The subscription now refreshes once connected, closing the gap
deterministically. Test holds the subscription unestablished while an
external opt-out lands and asserts the switch syncs on connect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ent reconciliation

Codex review round 9:

- A process that started while the shared config said opted-out never
  creates a PostHog client, so another process's re-enable left it dead
  until restart. capture() now kicks a lazy serialized initialize()
  when config says enabled but the client is null β€” rate-limited (30s)
  because the other enablement gates may legitimately keep it null.
- An API replacement (browser-mode reconnect) bumps the telemetry
  intent counter, so a late rejection from the superseded client cannot
  run failure reconciliation against state the new client has since
  confirmed; the config subscription re-establishes and re-syncs on the
  new client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex review round 10: the settle-replay callback captured the render's
refreshTelemetryFromBackend, so a write settling after an API
replacement replayed the deferred notification through the disconnected
client β€” a failed read there consumed the notification and stranded the
switch stale. Replays now go through a ref the api-change effect keeps
pointed at the current client generation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@asm
asm force-pushed the telemetry-settings-toggle branch from bd10767 to a898f93 Compare August 14, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant