Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/release/install-telemetry-removal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Install telemetry removal

The CLI no longer reports installations or changelog-detected updates to Pi.
This change is based on CLI PR #205 (`feat/staging-version-canaries`).
Inspection of `diffex-cloud` and `diffex-landing` found no install-report ingest implementation to remove.

## Reproduction and regression boundary

Run from `packages/coding-agent`:

```sh
node ../../node_modules/vitest/dist/cli.js --run test/install-telemetry-e2e.test.ts
```

The test starts the real source CLI with `--no-session --no-extensions --no-skills` in an isolated 100x30 PTY.
It supplies no provider credentials and intercepts every fetch before network access, with a socket guard for alternate HTTP clients.
Before removal, first install, upgrade, legacy opt-in, provider attribution opt-in, and normal startup attempted an anonymous `GET https://pi.dev/api/report-install?version=0.2.0` with a platform/runtime User-Agent.
Those five regression cases failed on the attempted request before the implementation changed.

After removal, all eight cases pass: first install, upgrade, current checkpoint, legacy opt-in, legacy opt-out, provider attribution opt-in, offline, and startup with version checks enabled.
Only the last case attempts a request, to the canonical Diffex latest-version endpoint.
Changelog acknowledgement still persists the current changelog checkpoint.
Each process exits through `/exit`, and the harness verifies removal of its temporary home, project, settings, and event record.

## Settings behavior

The old install-telemetry setting, environment-variable reader, settings callbacks, and reporting helper are removed.
Optional provider headers now use `enableProviderAttribution` and `DIFFEX_PROVIDER_ATTRIBUTION`.
The independent setting defaults off, so removing the old shared opt-out cannot re-enable optional headers.
Explicit provider attribution opt-in never triggers an install report.
Provider identity strings, OpenCode session headers, runtime tracing, and experimental analytics are separate functionality.

A real terminal smoke run opened `/settings`, searched for `attribution`, toggled **Provider attribution** from false to true, closed the selector, and exited.
The setting persisted and the recorded request list remained empty.
The settings selector tests also verify its compact layouts.

## Release checks

The staging client canary now rejects every unexpected startup request, including the former install-report exception.
The earlier [staging acceptance record](client-canaries.md) describes historical behavior before this removal.
This validation uses source clients with controlled HTTP responses; it does not deploy services or publish rebuilt CLI artifacts.
20 changes: 12 additions & 8 deletions packages/coding-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,14 +333,18 @@ If no extension or saved decision applies, `defaultProjectTrust` controls the fa

Use `/trust` in interactive mode to save a project trust decision for future sessions, including trust for the immediate parent folder. It writes `~/.diffex/agent/trust.json` only; the current session is not reloaded, so restart Diffex for changes to take effect.

### Telemetry and update checks
### Provider attribution and update checks

Diffex has two separate startup features:
Diffex does not send install or update telemetry reports.
The former `enableInstallTelemetry` setting and `DIFFEX_TELEMETRY` variable are no longer used.

- **Update check:** queries the upstream compatibility release service for the latest CLI version. Disable it with `DIFFEX_SKIP_VERSION_CHECK=1`. Disabling update checks only turns off this check.
- **Install/update telemetry:** after first install or a changelog-detected update, sends an anonymous version ping to the upstream compatibility telemetry service. This setting also controls optional provider attribution headers for OpenRouter, Cloudflare, and direct NVIDIA NIM requests. Opt out by setting `enableInstallTelemetry` to `false` in `settings.json`, or by setting `DIFFEX_TELEMETRY=0`. This does not disable update checks; Diffex may still contact the upstream compatibility release service unless update checks are disabled or offline mode is enabled.
Optional attribution headers for OpenRouter, Cloudflare, and direct NVIDIA NIM requests have a separate `enableProviderAttribution` setting, which defaults to `false`.
Enable it through **Provider attribution** in `/settings`, or override it with `DIFFEX_PROVIDER_ATTRIBUTION=1` (`0` disables it).
This control does not affect update checks, OpenCode session headers, or explicitly configured request headers.

Use `--offline` or `DIFFEX_OFFLINE=1` to disable all startup network operations described here, including update checks, package update checks, and install/update telemetry.
Startup update checks query `https://api.diffex.ai/api/latest-version`.
Disable automatic checks with `DIFFEX_SKIP_VERSION_CHECK=1`.
Use `--offline` or `DIFFEX_OFFLINE=1` to disable startup network operations, including update checks and package update checks.

---

Expand Down Expand Up @@ -698,9 +702,9 @@ diffex --thinking high "Solve this complex problem"
| `DIFFEX_CODING_AGENT_DIR` | Override config directory (default: `~/.diffex/agent`) |
| `DIFFEX_CODING_AGENT_SESSION_DIR` | Override session storage directory (overridden by `--session-dir`) |
| `DIFFEX_PACKAGE_DIR` | Override package directory (useful for Nix/Guix where store paths tokenize poorly) |
| `DIFFEX_OFFLINE` | Disable startup network operations, including update checks, package update checks, and install/update telemetry |
| `DIFFEX_SKIP_VERSION_CHECK` | Skip the upstream compatibility release check at startup |
| `DIFFEX_TELEMETRY` | Override install/update telemetry and provider attribution headers. Use `1`/`true`/`yes` to enable or `0`/`false`/`no` to disable. This does not disable update checks |
| `DIFFEX_OFFLINE` | Disable startup network operations, including update checks and package update checks |
| `DIFFEX_SKIP_VERSION_CHECK` | Skip the Diffex release check at startup |
| `DIFFEX_PROVIDER_ATTRIBUTION` | Override optional provider attribution headers: `1`/`true`/`yes` enables them; `0`/`false`/`no` disables them. Does not control update checks |
| `DIFFEX_CACHE_RETENTION` | Set to `long` for extended prompt cache (Anthropic: 1h, OpenAI: 24h) |
| `VISUAL`, `EDITOR` | Fallback external editor for Ctrl+G when `externalEditor` is unset; defaults to Notepad on Windows and `nano` elsewhere |

Expand Down
6 changes: 3 additions & 3 deletions packages/coding-agent/docs/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ These variables are read by Diffex itself:
| `DIFFEX_CODING_AGENT_DIR` | Override the config directory; default is `~/.diffex/agent` |
| `DIFFEX_CODING_AGENT_SESSION_DIR` | Override session storage; overridden by `--session-dir` |
| `DIFFEX_PACKAGE_DIR` | Override the package directory, useful for Nix/Guix store paths |
| `DIFFEX_OFFLINE` | Disable startup network operations, including update checks, package updates, and install/update telemetry |
| `DIFFEX_SKIP_VERSION_CHECK` | Disable the upstream compatibility release check |
| `DIFFEX_TELEMETRY` | Override install/update telemetry and provider attribution headers: `1`/`true`/`yes` or `0`/`false`/`no` |
| `DIFFEX_OFFLINE` | Disable startup network operations, including update checks and package updates |
| `DIFFEX_SKIP_VERSION_CHECK` | Disable the automatic Diffex release check |
| `DIFFEX_PROVIDER_ATTRIBUTION` | Override optional provider attribution headers: `1`/`true`/`yes` or `0`/`false`/`no`; defaults off |
| `DIFFEX_CACHE_RETENTION` | Set to `long` for extended provider prompt caching where supported |
| `DIFFEX_SHARE_VIEWER_URL` | Override the base URL used by `/share` |
| `DIFFEX_HARDWARE_CURSOR` | Set to `1` to show the hardware cursor; see [Terminal setup](terminal-setup.md) |
Expand Down
14 changes: 10 additions & 4 deletions packages/coding-agent/docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Use `/model` or Ctrl+L to select a model, then choose from its supported thinkin
| `quietStartup` | boolean | `false` | Hide startup header |
| `defaultProjectTrust` | string | `"ask"` | Fallback project trust behavior: `"ask"`, `"always"`, or `"never"`. Global setting only |
| `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
| `enableInstallTelemetry` | boolean | `true` | Send an anonymous install/update version ping after first install or changelog-detected updates. This does not control update checks |
| `enableProviderAttribution` | boolean | `false` | Add optional attribution headers to OpenRouter, Cloudflare, and direct NVIDIA NIM requests |
| `enableAnalytics` | boolean | `false` | Opt-in analytics data sharing. Currently only asked for during the experimental first-time setup (`DIFFEX_EXPERIMENTAL=1`) |
| `trackingId` | string | - | Analytics tracking identifier, generated when `enableAnalytics` is turned on |
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"`, `"fork"`, or `"none"` |
Expand All @@ -78,11 +78,17 @@ For VS Code, include `--wait` so Diffex resumes after the editor exits:
}
```

### Telemetry and update checks
### Provider attribution and update checks

`enableInstallTelemetry` only controls the anonymous install/update ping to the upstream compatibility telemetry service. Opting out of telemetry does not disable update checks; Diffex can still query the upstream compatibility release service for the latest CLI version.
Diffex does not send install or update telemetry reports.
The former `enableInstallTelemetry` setting and `DIFFEX_TELEMETRY` variable are ignored.
Optional provider attribution defaults off so removing the old shared setting cannot re-enable headers for users who previously opted out.
Use `enableProviderAttribution` or `DIFFEX_PROVIDER_ATTRIBUTION` to control optional OpenRouter, Cloudflare, and direct NVIDIA NIM headers independently.
The environment variable takes precedence: `1`/`true`/`yes` enables attribution; `0`/`false`/`no` disables it.
This setting does not control update checks, OpenCode session headers, or explicitly configured request headers.

Set `DIFFEX_SKIP_VERSION_CHECK=1` to disable the upstream compatibility release check. Use `--offline` or `DIFFEX_OFFLINE=1` to disable all startup network operations described here, including update checks, package update checks, and install/update telemetry.
Set `DIFFEX_SKIP_VERSION_CHECK=1` to disable automatic release checks against `https://api.diffex.ai/api/latest-version`.
Use `--offline` or `DIFFEX_OFFLINE=1` to disable startup network operations, including update checks and package update checks.

### Network

Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ ${chalk.bold("Environment Variables:")}
${ENV_SESSION_DIR.padEnd(32)} - Session storage directory (overridden by --session-dir)
DIFFEX_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
DIFFEX_OFFLINE - Disable startup network operations when set to 1/true/yes
DIFFEX_TELEMETRY - Override install telemetry when set to 1/true/yes or 0/false/no
DIFFEX_PROVIDER_ATTRIBUTION - Override optional provider attribution headers when set to 1/true/yes or 0/false/no
DIFFEX_SHARE_VIEWER_URL - Base URL for /share command (default: https://gist.github.com/)

${chalk.bold("Built-in Tool Names:")}
Expand Down
8 changes: 6 additions & 2 deletions packages/coding-agent/src/core/provider-attribution.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Api, Model, ProviderHeaders } from "@diffexai/diffex-ai";
import type { SettingsManager } from "./settings-manager.ts";
import { isInstallTelemetryEnabled } from "./telemetry.ts";

const OPENROUTER_HOST = "openrouter.ai";
const NVIDIA_NIM_HOST = "integrate.api.nvidia.com";
Expand Down Expand Up @@ -37,7 +36,12 @@ function getDefaultAttributionHeaders(
model: Model<Api>,
settingsManager: SettingsManager,
): Record<string, string> | undefined {
if (!isInstallTelemetryEnabled(settingsManager)) {
const attributionEnv = process.env.DIFFEX_PROVIDER_ATTRIBUTION;
const enabled =
attributionEnv !== undefined
? ["1", "true", "yes"].includes(attributionEnv.toLowerCase())
: settingsManager.getEnableProviderAttribution();
if (!enabled) {
return undefined;
}

Expand Down
12 changes: 6 additions & 6 deletions packages/coding-agent/src/core/settings-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export interface Settings {
shellCommandPrefix?: string; // Prefix prepended to every bash command (e.g., "shopt -s expand_aliases" for alias support)
npmCommand?: string[]; // Command used for npm package lookup/install operations, argv-style (e.g., ["mise", "exec", "node@20", "--", "npm"])
collapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)
enableInstallTelemetry?: boolean; // default: true - anonymous version/update ping after changelog-detected updates
enableProviderAttribution?: boolean; // default: false - optional provider attribution headers
enableAnalytics?: boolean; // default: false - opt-in analytics data sharing
trackingId?: string; // analytics tracking identifier, generated when analytics is enabled
packages?: PackageSource[]; // Array of npm/git package sources (string or object with filtering)
Expand Down Expand Up @@ -1036,13 +1036,13 @@ export class SettingsManager {
this.save();
}

getEnableInstallTelemetry(): boolean {
return this.settings.enableInstallTelemetry ?? true;
getEnableProviderAttribution(): boolean {
return this.settings.enableProviderAttribution ?? false;
}

setEnableInstallTelemetry(enabled: boolean): void {
this.globalSettings.enableInstallTelemetry = enabled;
this.markModified("enableInstallTelemetry");
setEnableProviderAttribution(enabled: boolean): void {
this.globalSettings.enableProviderAttribution = enabled;
this.markModified("enableProviderAttribution");
this.save();
}

Expand Down
13 changes: 0 additions & 13 deletions packages/coding-agent/src/core/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,6 @@ import type {
TelemetrySchemaSpanStartAttributes,
TelemetrySchemaSpanUnion,
} from "@diffexai/diffex-agent-core";
import type { SettingsManager } from "./settings-manager.ts";

function isTruthyEnvFlag(value: string | undefined): boolean {
if (!value) return false;
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
}

export function isInstallTelemetryEnabled(
settingsManager: SettingsManager,
telemetryEnv: string | undefined = process.env.DIFFEX_TELEMETRY,
): boolean {
return telemetryEnv !== undefined ? isTruthyEnvFlag(telemetryEnv) : settingsManager.getEnableInstallTelemetry();
}

const SKILL_OBSERVATION_ATTRIBUTES = {
"diffex.skill.id": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface SettingsConfig {
mermaidRenderingMode: MermaidRenderingMode;
showCacheMissNotices: boolean;
collapseChangelog: boolean;
enableInstallTelemetry: boolean;
enableProviderAttribution: boolean;
doubleEscapeAction: "fork" | "tree" | "none";
treeFilterMode: "default" | "no-tools" | "user-only" | "labeled-only" | "all";
showHardwareCursor: boolean;
Expand Down Expand Up @@ -95,7 +95,7 @@ export interface SettingsCallbacks {
onMermaidRenderingModeChange: (mode: MermaidRenderingMode) => void;
onShowCacheMissNoticesChange: (shown: boolean) => void;
onCollapseChangelogChange: (collapsed: boolean) => void;
onEnableInstallTelemetryChange: (enabled: boolean) => void;
onEnableProviderAttributionChange: (enabled: boolean) => void;
onDoubleEscapeActionChange: (action: "fork" | "tree" | "none") => void;
onTreeFilterModeChange: (mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all") => void;
onShowHardwareCursorChange: (enabled: boolean) => void;
Expand Down Expand Up @@ -558,10 +558,10 @@ export class SettingsSelectorComponent extends Container implements Focusable {
values: ["true", "false"],
},
{
id: "install-telemetry",
label: "Install telemetry",
description: "Send an anonymous version/update ping after changelog-detected updates",
currentValue: config.enableInstallTelemetry ? "true" : "false",
id: "provider-attribution",
label: "Provider attribution",
description: "Add optional attribution headers to OpenRouter, Cloudflare, and direct NVIDIA NIM requests",
currentValue: config.enableProviderAttribution ? "true" : "false",
values: ["true", "false"],
},
{
Expand Down Expand Up @@ -791,8 +791,8 @@ export class SettingsSelectorComponent extends Container implements Focusable {
case "quiet-startup":
callbacks.onQuietStartupChange(newValue === "true");
break;
case "install-telemetry":
callbacks.onEnableInstallTelemetryChange(newValue === "true");
case "provider-attribution":
callbacks.onEnableProviderAttributionChange(newValue === "true");
break;
case "default-project-trust": {
const defaultProjectTrust = DEFAULT_PROJECT_TRUST_BY_LABEL.get(newValue);
Expand Down
28 changes: 3 additions & 25 deletions packages/coding-agent/src/modes/interactive/interactive-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ import { SettingsManager, type TuiMode } from "../../core/settings-manager.ts";
import type { SkillReference } from "../../core/skills.ts";
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.ts";
import type { SourceInfo } from "../../core/source-info.ts";
import { isInstallTelemetryEnabled } from "../../core/telemetry.ts";
import type { TruncationResult } from "../../core/tools/truncate.ts";
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.ts";
import { getUsageCostBreakdown } from "../../core/usage-totals.ts";
Expand All @@ -123,7 +122,6 @@ import {
} from "../../utils/changelog.ts";
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.ts";
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.ts";
import { getDiffexUserAgent } from "../../utils/diffex-user-agent.ts";
import { parseGitUrl } from "../../utils/git.ts";
import { openBrowser } from "../../utils/open-browser.ts";
import { getCwdRelativePath } from "../../utils/paths.ts";
Expand Down Expand Up @@ -1336,33 +1334,13 @@ export class InteractiveMode {

if (selection.checkpointVersion && selection.checkpointVersion !== lastVersion) {
this.settingsManager.setLastChangelogVersion(selection.checkpointVersion);
this.reportInstallTelemetry(VERSION);
}

return selection.entries.length > 0
? selection.entries.map((entry) => normalizeChangelogLinks(entry.content, entry)).join("\n\n")
: undefined;
}

private reportInstallTelemetry(version: string): void {
if (process.env.DIFFEX_OFFLINE) {
return;
}

if (!isInstallTelemetryEnabled(this.settingsManager)) {
return;
}

void fetch(`https://pi.dev/api/report-install?version=${encodeURIComponent(version)}`, {
headers: {
"User-Agent": getDiffexUserAgent(version),
},
signal: AbortSignal.timeout(5000),
})
.then(() => undefined)
.catch(() => undefined);
}

private getMarkdownThemeWithSettings(): MarkdownTheme {
return {
...getMarkdownTheme(),
Expand Down Expand Up @@ -5224,7 +5202,7 @@ export class InteractiveMode {
hideThinkingBlock: this.hideThinkingBlock,
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
collapseChangelog: this.settingsManager.getCollapseChangelog(),
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
enableProviderAttribution: this.settingsManager.getEnableProviderAttribution(),
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
treeFilterMode: this.settingsManager.getTreeFilterMode(),
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
Expand Down Expand Up @@ -5302,8 +5280,8 @@ export class InteractiveMode {
onCollapseChangelogChange: (collapsed) => {
this.settingsManager.setCollapseChangelog(collapsed);
},
onEnableInstallTelemetryChange: (enabled) => {
this.settingsManager.setEnableInstallTelemetry(enabled);
onEnableProviderAttributionChange: (enabled) => {
this.settingsManager.setEnableProviderAttribution(enabled);
},
onQuietStartupChange: (enabled) => {
this.settingsManager.setQuietStartup(enabled);
Expand Down
Loading
Loading