Skip to content

feat(telemetry): leadbay_set_telemetry — enable/disable/status + honor opt-out#164

Draft
ArtyETH06 wants to merge 7 commits into
mainfrom
ArtyETH06/telemetry-toggle-tool
Draft

feat(telemetry): leadbay_set_telemetry — enable/disable/status + honor opt-out#164
ArtyETH06 wants to merge 7 commits into
mainfrom
ArtyETH06/telemetry-toggle-tool

Conversation

@ArtyETH06

Copy link
Copy Markdown
Contributor

What

Adds leadbay_set_telemetry — an in-product control to enable, disable, or check product-usage telemetry (product#3879). Telemetry stays ON by default (opt-out).

  • Tool (action: enable | disable | status, default status). statusGET /users/me; enable/disablePOST /users/telemetry then invalidateMe(). Idempotent — a no-op set reports changed:false and skips the write.
  • Per-user, account-stored preference (UserMePayload.telemetry_enabled) — so the control works the same on the hosted/web connector as on a local install. A local file would be wrong for the multi-tenant hosted server; the flag lives on the Leadbay account.
  • Hosted enforcementtelemetryHandleForRequest() reads the request user's telemetry_enabled and binds NOOP_TELEMETRY when they've opted out, so a disabled user's tool calls emit nothing, per-request, without affecting other tenants. Wired into both the streamable and SSE paths.
  • Contract — version 0.24.1 → 0.25.0 (package.json + server.json + npx pin), CHANGELOG, README "Privacy & telemetry", WORKFLOWS.md row Release MCP 0.10.0 — host widgets, routing schema, top-up, telemetry #45.

Part of a 3-PR feature (backend owns the contract → lands first)

  1. leadbay/api-specs#235 — the contract (User schema + POST /users/telemetry, 1.5 + 1.6).
  2. leadbay/backend#1929 — the column + route + PostHog gate.
  3. This (mcp) — the tool + hosted enforcement. Consumes the endpoint from #1929.

⚠️ Blocked on the backend endpoint (#1929) shipping — until then, status/enable/disable will 404. Merge order: api-specs → backend → this.

Verification

pnpm -r build && test && typecheck green (core 577, mcp 548 — includes new: the tool's status/enable/disable/idempotent cases, and the hosted opt-out enforcement incl. multi-tenant "A disabled → suppressed, B enabled → captured").

Note on scope: enforcement here is the hosted HTTP path (the multi-tenant remote where the users are). One item deliberately NOT done: leadbay_set_telemetry is not added to TOOLS_WITH_ROUTING in the routing audit — that's an existing test file (the "never modify existing test files" rule), and omission is safe (the tool ships with full routing frontmatter; it's just not asserted). Say the word if you want it added.

No eval coverage for this change (a backend-relay tool; not eval-tested prompt/widget behaviour).

Closes https://github.com/leadbay/product/issues/3879

@ArtyETH06 ArtyETH06 self-assigned this Jul 10, 2026
@github-actions github-actions Bot added the needs-manual-rebase PR conflicts with a version bump on main — needs a manual rebase label Jul 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

main shipped 0.24.2. Bump this PR's version above 0.24.2 (package.json + server.json) and re-anchor your CHANGELOG section above main's, then push. The sync bot won't edit CHANGELOG prose.

… honor opt-out

Adds an in-product control (product#3879) to enable, disable, or check
product-usage telemetry. Telemetry stays ON by default (opt-out).

- leadbay_set_telemetry composite (action: enable|disable|status, default
  status). status → GET /users/me; enable/disable → POST /users/telemetry then
  invalidateMe(). Idempotent (a no-op set reports changed:false, no write).
- UserMePayload.telemetry_enabled; the preference lives on the Leadbay account,
  so the control works the same on the hosted/web connector as on a local
  install (no local file — wrong for multi-tenant web).
- Hosted enforcement: telemetryHandleForRequest() reads the request user's
  telemetry_enabled and binds NOOP_TELEMETRY when they've opted out, so a
  disabled user's tool calls emit nothing — per-request, without affecting other
  tenants. Wired into both the streamable and SSE paths.
- Contract: version 0.24.1 → 0.25.0 (package.json + server.json + npx pin),
  CHANGELOG, README "Privacy & telemetry", WORKFLOWS.md row.

Consumes the backend telemetry_enabled field (leadbay/backend) + api-specs
contract. New tests: the tool (status/enable/disable/idempotent) and the hosted
opt-out enforcement (disabled user suppressed; multi-tenant A-off/B-on).

Co-Authored-By: Claude <noreply@anthropic.com>
@ArtyETH06 ArtyETH06 force-pushed the ArtyETH06/telemetry-toggle-tool branch from 055e3e9 to eb2de3f Compare July 13, 2026 23:22
github-actions Bot and others added 6 commits July 13, 2026 16:39
…g audit

- SSE opt-out is now LIVE: the session holds a mutable `suppressed` flag the
  bound handle reads on every capture, and POST /messages refreshes it (from the
  client's /users/me, freshened by the tool's invalidateMe) before dispatch — so
  a mid-session leadbay_set_telemetry disable takes effect on the next message
  instead of only after reconnect. bindTelemetryIdentity gains an optional
  isSuppressed predicate; the streamable path (fixed per-request) omits it.
- set_telemetry success copy no longer over-promises "no further events": it
  says the preference is saved and events stop per-request on the hosted
  connector / next session on a local install — honest about not-instant stdio.
- Add leadbay_set_telemetry to TOOLS_WITH_ROUTING so its routing block +
  examples are audited (the repo's new-routed-tool wiring contract).

New test: the live-suppression predicate flips the SAME bound handle without
rebuild. Full suite green (mcp 549, core 577).

Co-Authored-By: Claude <noreply@anthropic.com>
Codex P1: bindTelemetryIdentity gated the PostHog capture methods on isSuppressed
but left captureException inherited from base — so an opted-out SSE user's tool
errors still reached Sentry, even though the streamable path returns full NOOP
for the same preference. Gate captureException on isSuppressed too; when not
suppressed it passes through to base unchanged (ctx still carries region/org).

New test: a suppressed handle drops captureException while an enabled one forwards it.

Co-Authored-By: Claude <noreply@anthropic.com>
Codex P1: the tool's copy implied disabling here stops telemetry on a local
(stdio) install, but the stdio gate only reads LEADBAY_TELEMETRY_ENABLED and
never consults the account flag — so that promise was false locally. Per the
remote-only enforcement scope, remove the local-opt-out claim everywhere it
appeared and point local users at the env var instead:
- set-telemetry.ts success hint
- the tool description template (+ regenerated tool-descriptions.generated.ts)
- README "Privacy & telemetry" opt-out note
- WORKFLOWS.md row #45

The account flag is honored on the hosted connector per-request (unchanged);
local opt-out still needs LEADBAY_TELEMETRY_ENABLED=false, and the copy now
says exactly that.

Co-Authored-By: Claude <noreply@anthropic.com>
Codex P1: resolveTelemetryContext returned enabled:true when /users/me timed
out or errored, so a transient failure leaked telemetry for a user who had
opted out. Flip both failure paths to enabled:false (suppress) — the SSE
per-message refresh too. A privacy opt-out must not fail open: leaking an
opted-out user's events on transient API slowness is an unrecoverable consent
violation, whereas failing closed only drops a data point for an enabled user
on that one request (self-corrects next successful read). An absent
telemetry_enabled on a SUCCESSFUL read still means enabled (known preference).

New test: /users/me error → NOOP, nothing emitted.

Co-Authored-By: Claude <noreply@anthropic.com>
Codex P2: opting out returned full NOOP_TELEMETRY (streamable) / gated
captureFeedback (SSE), so leadbay_send_feedback — an explicit user-initiated
"deliver my message to the team" action — silently returned sent:false for
opted-out users. That's a regression: telemetry opt-out should suppress passive
analytics + error telemetry, not the user's own outbound feedback.

telemetryHandleForRequest now always returns the bound wrapper (with an
always-true isSuppressed predicate when opted out) instead of bare NOOP, and
bindTelemetryIdentity leaves captureFeedback un-gated. Everything else
(tool/composite/quota/topup/agent-memory/friction + captureException) stays
suppressed. Same for the SSE path.

New test: a fully-suppressed handle drops analytics but still delivers feedback.

Co-Authored-By: Claude <noreply@anthropic.com>
Codex P1: the per-message SSE opt-out refresh used resolveMe() (60s-cached), so
a disable made from ANOTHER connector/session — which only invalidateMe()s ITS
client, not this session's — was missed: the stale telemetry_enabled:true kept
this session emitting until the TTL expired. Use resolveMe(true) to force a
fresh read so a cross-session opt-out takes effect on the next message.

New test proves resolveMe(true) bypasses the cache (both forced reads hit the
backend; the second sees the flipped value).

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature needs-manual-rebase PR conflicts with a version bump on main — needs a manual rebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant