mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
fix(telemetry): suppress reporting from automated environments (#129155)
CI jobs are not installs; unchecked they outnumber operators by orders of magnitude and make version and platform counts meaningless. A configured telemetry endpoint still reports, so update-path E2E lanes keep working.
This commit is contained in:
committed by
GitHub
parent
bb0dc4629a
commit
6885eb69b0
@@ -165,6 +165,19 @@ even when `telemetry.enabled` is `true`. `DO_NOT_TRACK` does not disable the
|
||||
daily update check: OpenClaw sends the update-only `GET` request without a
|
||||
feature-statistics body.
|
||||
|
||||
## Automated environments
|
||||
|
||||
OpenClaw sends nothing when it detects an automated environment, meaning the
|
||||
`CI` environment variable is set to a truthy value. Continuous integration jobs
|
||||
are not installations: they would outnumber real operators by orders of
|
||||
magnitude and make version and platform counts meaningless, and your pipeline
|
||||
should not report to us on every job.
|
||||
|
||||
This applies to both tiers, so a CI job sends no update check and no feature
|
||||
statistics. Setting `OPENCLAW_TELEMETRY_ENDPOINT` overrides the suppression,
|
||||
because a configured endpoint means the run is deliberately exercising this
|
||||
path.
|
||||
|
||||
## Disable every automatic update request
|
||||
|
||||
To go fully dark, disable the existing startup update check:
|
||||
|
||||
@@ -10,6 +10,7 @@ import { runCommandWithRuntime } from "./cli-utils.js";
|
||||
|
||||
const TELEMETRY_REASON_LABELS = {
|
||||
enabled: "enabled in configuration",
|
||||
"automated-environment": "disabled in an automated environment (CI is set)",
|
||||
"do-not-track": "disabled by DO_NOT_TRACK",
|
||||
"config-disabled": "disabled in configuration",
|
||||
"never-asked": "consent has not been requested",
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
type OpenClawTestState,
|
||||
} from "../test-utils/openclaw-test-state.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { buildTelemetryPayload, checkTelemetryUpdate } from "./telemetry.js";
|
||||
import {
|
||||
buildTelemetryPayload,
|
||||
checkTelemetryUpdate,
|
||||
resolveTelemetryStatus,
|
||||
} from "./telemetry.js";
|
||||
|
||||
const NOW = Date.parse("2026-08-23T12:00:00.000Z");
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
@@ -86,6 +90,7 @@ describe("anonymous telemetry", () => {
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-telemetry-",
|
||||
env: {
|
||||
CI: undefined,
|
||||
DO_NOT_TRACK: undefined,
|
||||
OPENCLAW_NIX_MODE: undefined,
|
||||
OPENCLAW_NO_AUTO_UPDATE: undefined,
|
||||
@@ -360,6 +365,35 @@ describe("anonymous telemetry", () => {
|
||||
expect(readConfigMachineState(TELEMETRY_STATE_KEY)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("never sends a request from an automated environment", async () => {
|
||||
setTestEnvValue("CI", "true");
|
||||
|
||||
await expect(
|
||||
checkTelemetryUpdate(createFeatureConfig(), {
|
||||
surface: "gateway",
|
||||
fetchImpl: globalThis.fetch,
|
||||
nowMs: NOW,
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
|
||||
expect(mockHttp.requests()).toHaveLength(0);
|
||||
expect(readConfigMachineState(TELEMETRY_STATE_KEY)).toBeUndefined();
|
||||
expect(resolveTelemetryStatus(createFeatureConfig()).reason).toBe("automated-environment");
|
||||
});
|
||||
|
||||
it("still reports from an automated environment when an endpoint is configured for it", async () => {
|
||||
const customEndpoint = "https://telemetry.example.invalid/api/latest-version";
|
||||
setTestEnvValue("CI", "true");
|
||||
setTestEnvValue("OPENCLAW_TELEMETRY_ENDPOINT", customEndpoint);
|
||||
mockHttp.intercept({ url: customEndpoint, reply: { json: { version: "2026.8.24" } } });
|
||||
|
||||
await expect(
|
||||
checkTelemetryUpdate({}, { surface: "gateway", fetchImpl: globalThis.fetch, nowMs: NOW }),
|
||||
).resolves.toEqual({ version: "2026.8.24" });
|
||||
|
||||
expect(mockHttp.requests()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("never sends a request for Nix-managed installations", async () => {
|
||||
setTestEnvValue("OPENCLAW_NIX_MODE", "1");
|
||||
|
||||
|
||||
+18
-1
@@ -54,6 +54,7 @@ type TelemetryPayload = {
|
||||
|
||||
type TelemetryStatusReason =
|
||||
| "enabled"
|
||||
| "automated-environment"
|
||||
| "do-not-track"
|
||||
| "config-disabled"
|
||||
| "never-asked"
|
||||
@@ -73,10 +74,24 @@ const TelemetryResponseSchema = z.object({
|
||||
let lastFailedAttempt: { at: number; endpoint: string; stateDirectory?: string } | undefined;
|
||||
let inFlightUpdate: Promise<TelemetryUpdate | null> | undefined;
|
||||
|
||||
/**
|
||||
* CI jobs are not installs. Left unchecked they outnumber operators by orders of
|
||||
* magnitude and make version and platform counts meaningless, and someone else's
|
||||
* pipeline should not report to us on every job either. A configured endpoint
|
||||
* means the caller is deliberately exercising this path, so it still reports.
|
||||
*/
|
||||
function isAutomatedEnvironment(): boolean {
|
||||
if (process.env.OPENCLAW_TELEMETRY_ENDPOINT?.trim()) {
|
||||
return false;
|
||||
}
|
||||
return isTruthyEnvValue(process.env.CI);
|
||||
}
|
||||
|
||||
function isUpdateCheckDisabled(config: OpenClawConfig): boolean {
|
||||
return (
|
||||
config.update?.checkOnStart === false ||
|
||||
isTruthyEnvValue(process.env.OPENCLAW_NO_AUTO_UPDATE) ||
|
||||
isAutomatedEnvironment() ||
|
||||
resolveIsNixMode()
|
||||
);
|
||||
}
|
||||
@@ -132,7 +147,9 @@ export function resolveTelemetryStatus(config: OpenClawConfig): {
|
||||
lastPingAt?: number;
|
||||
} {
|
||||
let reason: TelemetryStatusReason;
|
||||
if (isUpdateCheckDisabled(config)) {
|
||||
if (isAutomatedEnvironment()) {
|
||||
reason = "automated-environment";
|
||||
} else if (isUpdateCheckDisabled(config)) {
|
||||
reason = "update-disabled";
|
||||
} else if (isDoNotTrackEnabled()) {
|
||||
reason = "do-not-track";
|
||||
|
||||
Reference in New Issue
Block a user