Skip to content

Commit 1577060

Browse files
iamcipherdevjrusso1020claude
authored
fix(media-use): honor telemetryEnabled: false from shared config in optedOut (#5490)
* fix(media-use): honor telemetryEnabled: false from shared config in optedOut The CLI's 'hyperframes telemetry disable' writes telemetryEnabled: false to ~/.hyperframes/config.json, but the media-use scripts' optedOut() only checked env vars, so transcribe.mjs, transcript-cut.mjs and audio-duck.mjs kept sending PostHog events for users who had opted out through the CLI. optedOut() now also returns true when the shared config has telemetryEnabled === false, reusing the readSharedConfig() helper already in the same file. Applied byte-identically to both copies of telemetry.mjs (skills/media-use/scripts/lib and packages/cli/src/media-use/lib, per the copy-parity rule), with tests in both test files. Closes #5465 * test(media-use): sandbox HOME in the telemetry opt-out test and regenerate the skills manifest Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: James <james.russo@heygen.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent aa9413b commit 1577060

5 files changed

Lines changed: 103 additions & 4 deletions

File tree

‎packages/cli/src/media-use/lib/telemetry.mjs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ function posthogHost() {
3535
return override || POSTHOG_HOST;
3636
}
3737

38-
/** True when telemetry must NOT be sent (opt-out envs, CI, dev). */
38+
/** True when telemetry must NOT be sent (opt-out envs, CI, dev, shared config). */
3939
export function optedOut() {
4040
return (
41+
// `hyperframes telemetry disable` writes telemetryEnabled: false to the
42+
// shared config; one opt-out must cover the CLI and the media-use scripts.
43+
readSharedConfig().telemetryEnabled === false ||
4144
process.env.HYPERFRAMES_NO_TELEMETRY === "1" ||
4245
process.env.DO_NOT_TRACK === "1" ||
4346
process.env.CI === "true" ||

‎packages/cli/src/media-use/lib/telemetry.test.mjs‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@ test("optedOut respects DO_NOT_TRACK / HYPERFRAMES_NO_TELEMETRY / CI", () => {
5757
}
5858
});
5959

60+
test("optedOut honors telemetryEnabled: false from the shared config", () => {
61+
const savedEnv = { ...process.env };
62+
const { root, home } = sandbox();
63+
try {
64+
withoutTelemetryOptOut();
65+
const configPath = join(home, ".hyperframes", "config.json");
66+
assert.equal(optedOut(), false, "no config: tracking allowed");
67+
mkdirSync(join(home, ".hyperframes"), { recursive: true });
68+
writeFileSync(configPath, JSON.stringify({ telemetryEnabled: false }));
69+
assert.equal(optedOut(), true, "telemetryEnabled: false opts out");
70+
writeFileSync(configPath, JSON.stringify({ telemetryEnabled: true }));
71+
assert.equal(optedOut(), false, "telemetryEnabled: true allows tracking");
72+
writeFileSync(configPath, "{not json");
73+
assert.equal(optedOut(), false, "unreadable config: tracking allowed");
74+
} finally {
75+
restoreEnv(savedEnv);
76+
rmSync(root, { recursive: true, force: true });
77+
}
78+
});
79+
6080
test("track is a no-op (no network, resolves) when opted out", async () => {
6181
const savedEnv = { ...process.env };
6282
const originalFetch = globalThis.fetch;

‎skills-manifest.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@
5454
"files": 1
5555
},
5656
"media-use": {
57-
"hash": "efb4902546396bfb",
58-
"files": 111
57+
"hash": "0b6e01d1fecb39d6",
58+
"files": 112
5959
},
6060
"motion-graphics": {
6161
"hash": "6db77031ad839f6a",

‎skills/media-use/scripts/lib/telemetry.mjs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ function posthogHost() {
3535
return override || POSTHOG_HOST;
3636
}
3737

38-
/** True when telemetry must NOT be sent (opt-out envs, CI, dev). */
38+
/** True when telemetry must NOT be sent (opt-out envs, CI, dev, shared config). */
3939
export function optedOut() {
4040
return (
41+
// `hyperframes telemetry disable` writes telemetryEnabled: false to the
42+
// shared config; one opt-out must cover the CLI and the media-use scripts.
43+
readSharedConfig().telemetryEnabled === false ||
4144
process.env.HYPERFRAMES_NO_TELEMETRY === "1" ||
4245
process.env.DO_NOT_TRACK === "1" ||
4346
process.env.CI === "true" ||
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import assert from "node:assert/strict";
2+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3+
import { tmpdir } from "node:os";
4+
import { dirname, join } from "node:path";
5+
import { test } from "node:test";
6+
import { optedOut } from "./telemetry.mjs";
7+
8+
// optedOut() reads ~/.hyperframes/config.json and env vars. Point HOME at a temp
9+
// dir so the run never touches the real config, and pin the env opt-outs to a
10+
// neutral state; restore both afterwards.
11+
const ENV_KEYS = [
12+
"HOME",
13+
"USERPROFILE",
14+
"HYPERFRAMES_NO_TELEMETRY",
15+
"DO_NOT_TRACK",
16+
"CI",
17+
"NODE_ENV",
18+
];
19+
const savedEnv = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
20+
const home = mkdtempSync(join(tmpdir(), "mu-telemetry-"));
21+
const configPath = join(home, ".hyperframes", "config.json");
22+
23+
function writeConfig(contents) {
24+
if (contents === null) {
25+
rmSync(configPath, { force: true });
26+
} else {
27+
mkdirSync(dirname(configPath), { recursive: true });
28+
writeFileSync(configPath, contents);
29+
}
30+
}
31+
32+
test.before(() => {
33+
for (const key of ENV_KEYS) delete process.env[key];
34+
process.env.HOME = process.env.USERPROFILE = home;
35+
});
36+
37+
test.after(() => {
38+
for (const key of ENV_KEYS) {
39+
if (savedEnv[key] === undefined) delete process.env[key];
40+
else process.env[key] = savedEnv[key];
41+
}
42+
rmSync(home, { recursive: true, force: true });
43+
});
44+
45+
test("optedOut is false with no config and no opt-out env", () => {
46+
writeConfig(null);
47+
assert.equal(optedOut(), false);
48+
});
49+
50+
test("optedOut honors telemetryEnabled: false from the shared config", () => {
51+
writeConfig(JSON.stringify({ telemetryEnabled: false }));
52+
assert.equal(optedOut(), true);
53+
});
54+
55+
test("optedOut is false when the shared config enables telemetry", () => {
56+
writeConfig(JSON.stringify({ telemetryEnabled: true }));
57+
assert.equal(optedOut(), false);
58+
});
59+
60+
test("optedOut treats an unreadable shared config as no opt-out", () => {
61+
writeConfig("{not json");
62+
assert.equal(optedOut(), false);
63+
});
64+
65+
test("optedOut still honors the env opt-outs", () => {
66+
writeConfig(JSON.stringify({ telemetryEnabled: true }));
67+
process.env.DO_NOT_TRACK = "1";
68+
try {
69+
assert.equal(optedOut(), true);
70+
} finally {
71+
delete process.env.DO_NOT_TRACK;
72+
}
73+
});

0 commit comments

Comments
 (0)