Skip to content

feat: send one final telemetry_opted_out event when telemetry is disabled - #1501

Open
ralphstodomingo wants to merge 3 commits into
feat/telemetry-user-identityfrom
feat/telemetry-opt-out-marker
Open

ralphstodomingo wants to merge 3 commits into
feat/telemetry-user-identityfrom
feat/telemetry-opt-out-marker

Conversation

@ralphstodomingo

@ralphstodomingo ralphstodomingo commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1499

Stacked on #1500 (user id and tenant on events); review this one after it, or read only the last commit.

What

When telemetry is disabled, a machine that has sent telemetry before sends exactly one final telemetry_opted_out event, and then nothing until telemetry is turned back on.

  • New telemetry/opt-out.ts: the marker file ~/.altimate/telemetry-opted-out next to the machine id, and a read of the machine id that never mints one.
  • telemetry/index.ts: recordOptOut(source) runs from both disabled branches of init (env for the environment variables, config for telemetry.disabled). It stops at once when the marker exists or no machine id exists, applies the same sink rules as a normal init (test runners never reach the production sink), uses the identity cached by an earlier signed-in run without any request, posts one envelope with a 3 s cap, and writes the marker only once the ingest endpoint accepted it, so an offline launch tries again next time. The event carries machine_id, cli_version, opt_out_source, and user_id and tenant when they were cached. A normal init clears the marker.
  • The event type is declared in the union for documentation; it is never sent through track().

Why

Telemetry is on by default and can be turned off. An opted-out machine goes silent, which on the dashboards is indistinguishable from a client that stopped emitting, so missing events get investigated as bugs. With the opt-out as the last event, the dashboards can state the opt-out count and attribute the silence that follows. Opt-outs set before the first launch produce no event; the dashboard states that remainder as unknowable.

Verification

  • bun test test/altimate/telemetry/opt-out.test.ts: 4 tests. The final event and its properties against a stubbed ingest endpoint, with the identity taken from the cache and no request to resolve it; nothing sent afterwards and nothing on a later init; no event and no marker without a machine id; the marker only after a 2xx (a 500 leaves it for the next launch); a normal init removes the marker.
  • Identity suite (3) and the existing telemetry suites pass; tsgo --noEmit clean; oxlint and prettier clean on the new files.

Review log (claims and residuals Codex reviews against): #1501 (comment)

🤖 Generated with Claude Code

https://claude.ai/code/session_011AhtJPr3nmT1Po92ZQk4VE


Summary by cubic

Sends one final telemetry_opted_out event when telemetry is disabled, so dashboards can distinguish opted-out machines from clients that stopped emitting. Closes #1499.

  • The event carries the machine id, CLI version, opt-out source, and cached user id and tenant; it is sent at most once, guarded by a telemetry-opted-out marker file next to the machine id.
  • The marker is written only after the ingest endpoint accepted the event, so an offline launch retries next time; a normal init clears it, so a later opt-out is recorded again.
  • A send still in flight when telemetry is re-enabled is dropped, so it never recreates the marker for the new lifecycle.
  • The machine id is read without minting one, so machines that never sent telemetry send nothing; the opt-out path never runs under the force flag or a test runner unless a test opts in.

Written for commit c858470. Summary will update on new commits.

View guided diff Turn on auto-fix

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b374b4bf-0924-4314-aa2a-ee21b8544813

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@ralphstodomingo
ralphstodomingo force-pushed the feat/telemetry-opt-out-marker branch from ce45123 to 0b96db6 Compare October 15, 2026 05:34
@ralphstodomingo
ralphstodomingo marked this pull request as ready for review October 15, 2026 07:31
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

Codex review log

Stacked on #1500; review the last commit only. Review runs against these claims. Report only a reproducible trace that violates a numbered claim; instances of the disclosed residuals are not findings. A round with no claim violation ends review.

Claims

  • C1 With telemetry disabled by ALTIMATE_TELEMETRY_DISABLED, OPENCODE_DISABLE_TELEMETRY or telemetry.disabled, Telemetry.init() sends at most one telemetry_opted_out envelope per machine across processes, and nothing else: track() drops every event, and after the marker ~/.altimate/telemetry-opted-out exists no request is made on any later init.
  • C2 The event is sent only when ~/.altimate/machine-id already holds a UUID; recordOptOut() never mints a machine id, never creates the .altimate directory before the send succeeded, and never calls the backend (identity comes from TelemetryIdentity.cached(), which is file-only).
  • C3 The marker is written only after the ingest endpoint answered 2xx; a non-2xx, a thrown fetch or the 3 s abort leaves no marker, so the next init tries once more.
  • C4 The envelope's baseData.name is telemetry_opted_out; its properties carry machine_id, cli_version, source, launch_id, opt_out_source (env or config), and user_id plus tenant only when a cached identity matches the credential on disk; tags["ai.user.id"] is the uuid when cached, else the machine id.
  • C5 After recordOptOut() returns, by any path, the module's machineId and identity are empty again, and enabled stays false.
  • C6 A normal (enabled) init removes the marker, so a later opt-out is recorded again; nothing else reads or writes the marker.
  • C7 Under a test runner (NODE_ENV=test, BUN_TEST, VITEST, JEST_WORKER_ID) recordOptOut() returns before touching any file unless Telemetry.allowOptOutUnderTestRunner(true) was called, and it returns whenever ALTIMATE_TELEMETRY_FORCE=true, regardless of the sink.
  • C8 OptOutMarker paths resolve through Global.Path.home, so OPENCODE_TEST_HOME redirects both the marker and the machine-id read; in a real session that is the same directory getOrCreateMachineId() uses.
  • C9 recordOptOut() is not awaited by doInit(); an opted-out launch never waits on the network.

Residuals

  • R1 A short-lived process may exit before the 3 s send completes; the event is then sent by a later launch (C3). Accepted.
  • R2 An opt-out set before the machine ever ran with telemetry on produces no event and no marker; the dashboard states that remainder as unknowable.
  • R3 Two concurrent processes on one machine starting while opted out and unmarked can both send the event once; the marker then stops both. Accepted as a rare double count.
  • R4 The automated-run.test.ts isolation helper now also sets OPENCODE_TEST_HOME; that is a test-only change so code keyed on Global.Path.home sees the temp home under Bun, whose os.homedir() does not follow HOME.

@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review against the numbered claims and the disclosed residuals in the review-log comment on this PR: report only a reproducible trace that violates a numbered claim. Instances of the disclosed residuals are not findings. A round with no claim violation ends review. This PR is stacked on #1500: review only the last commit (the opt-out marker), not the identity change beneath it.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T07:49:59.027090Z 6e425e4 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

const explicit = process.env.APPLICATIONINSIGHTS_CONNECTION_STRING
const cfg = parseConnectionString(explicit ?? DEFAULT_CONNECTION_STRING)
if (!cfg) return
machineId = id

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Mutating module-level machineId across await TelemetryIdentity.cached() leaks or resets machineId on concurrent init() cycles

recordOptOut() is fired without await (void recordOptOut(...)), so a shutdown() followed by a new init() in the same process (such as across session loops or in test suites) can run concurrently with an in-flight recordOptOut() paused at await TelemetryIdentity.cached() (line 2179). During that window, the new doInit() shares the mutated machineId, and when recordOptOut() resumes at lines 2184–2185 (or in catch at lines 2201–2202), it overwrites machineId = "" and identity = undefined, clobbering the active session's state. Resolve const cachedIdentity = await TelemetryIdentity.cached() before mutating module state so machineId and identity are only set and restored synchronously around toAppInsightsEnvelopes(...).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3032626: toAppInsightsEnvelopes() takes the machine id and identity as arguments, so recordOptOut() never writes module state; a later fix (6e425e4) also fences the whole send on the lifecycle generation.

* that never sent telemetry has nothing to mark. */
export function readMachineId(): string | undefined {
try {
const raw = fs.readFileSync(path.join(dir(), "machine-id"), "utf8").trim()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: readMachineId() uses unbounded readFileSync instead of lstatSync and size-capped reading like getOrCreateMachineId()

In src/altimate/util/machine-id.ts, reading ~/.altimate/machine-id explicitly checks lstatSync(idPath).isFile() and caps reads at 512 bytes via a file descriptor to avoid following symlinks or slurping multi-MB files into memory before regex validation. Here, fs.readFileSync(path.join(dir(), "machine-id"), "utf8") follows symlinks and reads the entire file into memory unrestricted.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3032626: readMachineId() now uses readExistingMachineId() from util/machine-id.ts (lstat regular-file check, 512-byte cap, UUID validation), with a test for a corrupt or oversized file.

@kilo-code-bot

kilo-code-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/opencode/src/altimate/telemetry/opt-out.ts
  • packages/opencode/src/altimate/util/machine-id.ts
  • packages/opencode/test/altimate/telemetry/opt-out.test.ts
  • packages/opencode/test/telemetry/automated-run.test.ts
Previous Review Summaries (4 snapshots, latest commit ddff5b3)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit ddff5b3)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/opencode/src/altimate/telemetry/opt-out.ts
  • packages/opencode/src/altimate/util/machine-id.ts
  • packages/opencode/test/altimate/telemetry/opt-out.test.ts
  • packages/opencode/test/telemetry/automated-run.test.ts

Previous review (commit d40c562)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/opencode/src/altimate/telemetry/opt-out.ts
  • packages/opencode/src/altimate/util/machine-id.ts
  • packages/opencode/test/altimate/telemetry/opt-out.test.ts
  • packages/opencode/test/telemetry/automated-run.test.ts

Previous review (commit 6e425e4)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/opencode/src/altimate/telemetry/opt-out.ts
  • packages/opencode/src/altimate/util/machine-id.ts
  • packages/opencode/test/altimate/telemetry/opt-out.test.ts
  • packages/opencode/test/telemetry/automated-run.test.ts

Previous review (commit 0b96db6)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/altimate/telemetry/index.ts 2178 Mutating module-level machineId across await TelemetryIdentity.cached() leaks or resets machineId on concurrent init() cycles
packages/opencode/src/altimate/telemetry/opt-out.ts 33 readMachineId() uses unbounded readFileSync instead of lstatSync and size-capped reading like getOrCreateMachineId()
Files Reviewed (4 files)
  • packages/opencode/src/altimate/telemetry/index.ts - 1 issue
  • packages/opencode/src/altimate/telemetry/opt-out.ts - 1 issue
  • packages/opencode/test/altimate/telemetry/opt-out.test.ts
  • packages/opencode/test/telemetry/automated-run.test.ts

Fix these issues in Kilo Cloud


Reviewed by gemini-4-argon · Input: 85.3K · Output: 1.9K · Cached: 279.8K

Review guidance: REVIEW.md from base branch feat/telemetry-user-identity

@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: 0b96db6fbc

ℹ️ 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 packages/opencode/src/altimate/telemetry/index.ts

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/opencode/test/altimate/telemetry/opt-out.test.ts
Comment thread packages/opencode/src/altimate/telemetry/opt-out.ts Outdated
Comment thread packages/opencode/src/altimate/telemetry/opt-out.ts Outdated
Comment thread packages/opencode/src/altimate/telemetry/index.ts Outdated
Comment thread packages/opencode/src/altimate/telemetry/index.ts
Comment thread packages/opencode/src/altimate/telemetry/index.ts
@ralphstodomingo
ralphstodomingo force-pushed the feat/telemetry-opt-out-marker branch from 0b96db6 to 6e425e4 Compare October 15, 2026 07:45
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review against the numbered claims and the disclosed residuals in the review-log comment on this PR: report only a reproducible trace that violates a numbered claim. Instances of the disclosed residuals are not findings. A round with no claim violation ends review. Round 2: the pushes since round 1 fence the send on a lifecycle generation (the P2 trace), build the envelope from local values, and make the machine-id read bounded; this PR is stacked on #1500, review only the commits above it.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 6e425e4051

ℹ️ 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".

@ralphstodomingo
ralphstodomingo force-pushed the feat/telemetry-opt-out-marker branch 2 times, most recently from d40c562 to ddff5b3 Compare October 15, 2026 08:07
ralphstodomingo and others added 3 commits October 15, 2026 16:11
…bled

A machine that has sent telemetry before sends the opt-out once (machine id,
version, how it was set, cached user id and tenant) and nothing after; a marker
next to the machine id keeps it to once and is cleared when telemetry is
re-enabled.

Closes #1499

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AhtJPr3nmT1Po92ZQk4VE
… state alone

readExistingMachineId() shares the regular-file, size-cap and UUID checks of
the minting read; the envelope builder takes the machine id and identity as
arguments so recordOptOut() never mutates module state an enabled init may use.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AhtJPr3nmT1Po92ZQk4VE
…utdown

The send carries the lifecycle it started in; shutdown() bumps it, so a task
still pending when telemetry is re-enabled neither posts nor recreates the
marker. Marker removal is best effort, and the suite drops the force flag.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AhtJPr3nmT1Po92ZQk4VE
@ralphstodomingo
ralphstodomingo force-pushed the feat/telemetry-opt-out-marker branch from ddff5b3 to c858470 Compare October 15, 2026 08:12

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant