Skip to content

Commit 1252a3d

Browse files
fix: send owner-qualified install telemetry (#102377)
1 parent e8bd3ae commit 1252a3d

4 files changed

Lines changed: 42 additions & 84 deletions

File tree

src/cli/skills-cli.clawhub-install.e2e.test.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,23 @@ describe("openclaw skills install ClawHub GitHub-backed E2E", () => {
108108
const registry = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
109109
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-clawhub-cli-e2e-"));
110110
try {
111-
const result = await spawnOpenClaw(["skills", "install", "aiq-deploy", "--global"], {
112-
cwd: process.cwd(),
113-
env: {
114-
...process.env,
115-
OPENCLAW_STATE_DIR: stateDir,
116-
OPENCLAW_CONFIG_PATH: path.join(stateDir, "openclaw.json"),
117-
OPENCLAW_CLAWHUB_URL: registry,
118-
OPENCLAW_CLAWHUB_TOKEN: "test-token",
119-
OPENCLAW_CLAWHUB_GITHUB_CODELOAD_BASE_URL: registry,
120-
CLAWHUB_DISABLE_TELEMETRY: "",
121-
CLAWDHUB_DISABLE_TELEMETRY: "",
122-
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
111+
const result = await spawnOpenClaw(
112+
["skills", "install", "@demo-owner/aiq-deploy", "--global"],
113+
{
114+
cwd: process.cwd(),
115+
env: {
116+
...process.env,
117+
OPENCLAW_STATE_DIR: stateDir,
118+
OPENCLAW_CONFIG_PATH: path.join(stateDir, "openclaw.json"),
119+
OPENCLAW_CLAWHUB_URL: registry,
120+
OPENCLAW_CLAWHUB_TOKEN: "test-token",
121+
OPENCLAW_CLAWHUB_GITHUB_CODELOAD_BASE_URL: registry,
122+
CLAWHUB_DISABLE_TELEMETRY: "",
123+
CLAWDHUB_DISABLE_TELEMETRY: "",
124+
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
125+
},
123126
},
124-
});
127+
);
125128

126129
expect(result.status, result.stderr || result.stdout).toBe(0);
127130
await expect(
@@ -137,11 +140,10 @@ describe("openclaw skills install ClawHub GitHub-backed E2E", () => {
137140
throw new Error(`Expected one install telemetry request, saw: ${requestLog.join(", ")}`);
138141
}
139142
expect(telemetryBodies[0]).toMatchObject({
140-
roots: [
141-
{
142-
skills: [{ slug: "aiq-deploy", version: commit }],
143-
},
144-
],
143+
event: "install",
144+
slug: "aiq-deploy",
145+
ownerHandle: "demo-owner",
146+
version: commit,
145147
});
146148
} finally {
147149
await new Promise<void>((resolve) => {

src/infra/clawhub.ts

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,6 @@ export type ClawHubDownloadResult = {
404404
cleanup: () => Promise<void>;
405405
};
406406

407-
export type ClawHubInstallTelemetrySkill = {
408-
version?: string | null;
409-
};
410-
411407
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
412408

413409
type ClawHubRequestParams = {
@@ -1619,21 +1615,20 @@ export async function downloadClawHubGitHubSkillArchive(params: {
16191615
export async function reportClawHubSkillInstallTelemetry(params: {
16201616
baseUrl?: string;
16211617
token?: string;
1622-
root: string;
1623-
skills: Record<string, ClawHubInstallTelemetrySkill>;
1618+
slug: string;
1619+
ownerHandle?: string;
1620+
version?: string | null;
16241621
timeoutMs?: number;
16251622
fetchImpl?: FetchLike;
16261623
}): Promise<void> {
16271624
const token = normalizeOptionalString(params.token) ?? (await resolveClawHubAuthToken());
16281625
if (!token || isClawHubTelemetryDisabled()) {
16291626
return;
16301627
}
1631-
const skills = Object.entries(params.skills)
1632-
.map(([slug, entry]) => ({
1633-
slug,
1634-
version: entry.version ?? null,
1635-
}))
1636-
.filter((entry) => entry.slug.length > 0);
1628+
const slug = params.slug.trim();
1629+
if (!slug) {
1630+
return;
1631+
}
16371632

16381633
const { response, url, hasToken } = await clawhubRequest({
16391634
baseUrl: params.baseUrl,
@@ -1643,13 +1638,10 @@ export async function reportClawHubSkillInstallTelemetry(params: {
16431638
timeoutMs: params.timeoutMs,
16441639
fetchImpl: params.fetchImpl,
16451640
json: {
1646-
roots: [
1647-
{
1648-
rootId: digestSha256Hex(path.resolve(params.root)),
1649-
label: formatTelemetryRootLabel(params.root),
1650-
skills,
1651-
},
1652-
],
1641+
event: "install",
1642+
slug,
1643+
...(params.ownerHandle ? { ownerHandle: params.ownerHandle } : {}),
1644+
version: params.version ?? undefined,
16531645
},
16541646
});
16551647
if (!response.ok) {
@@ -1665,20 +1657,6 @@ function isClawHubTelemetryDisabled(): boolean {
16651657
return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
16661658
}
16671659

1668-
function formatTelemetryRootLabel(root: string): string {
1669-
const home = os.homedir();
1670-
const absolute = path.resolve(root);
1671-
if (absolute === home) {
1672-
return "~";
1673-
}
1674-
const normalized = absolute.replaceAll("\\", "/");
1675-
const normalizedHome = home.replaceAll("\\", "/");
1676-
const withinHome = normalized.startsWith(`${normalizedHome}/`);
1677-
const stripped = withinHome ? normalized.slice(normalizedHome.length + 1) : normalized;
1678-
const tail = stripped.split("/").filter(Boolean).slice(-2).join("/");
1679-
return withinHome ? `~/${tail}` : tail || absolute;
1680-
}
1681-
16821660
/** Resolves the preferred latest package version from detail metadata. */
16831661
export function resolveLatestVersionFromPackage(detail: ClawHubPackageDetail): string | null {
16841662
return detail.package?.latestVersion ?? detail.package?.tags?.latest ?? null;

src/skills/lifecycle/clawhub.test.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -318,23 +318,9 @@ describe("skills-clawhub", () => {
318318
expect(archiveCleanupMock).toHaveBeenCalledTimes(1);
319319
expect(reportClawHubSkillInstallTelemetryMock).toHaveBeenCalledWith({
320320
baseUrl: undefined,
321-
root: "/tmp/workspace",
322-
skills: expect.objectContaining({
323-
agentreceipt: expect.objectContaining({
324-
version: "1.0.0",
325-
installedAt: expect.any(Number),
326-
registry: "https://clawhub.ai",
327-
}),
328-
}),
321+
slug: "agentreceipt",
322+
version: "1.0.0",
329323
});
330-
const telemetrySkills = reportClawHubSkillInstallTelemetryMock.mock.calls[0]?.[0]?.skills as
331-
| Record<string, Record<string, unknown>>
332-
| undefined;
333-
expect(Object.keys(telemetrySkills?.agentreceipt ?? {}).toSorted()).toEqual([
334-
"installedAt",
335-
"registry",
336-
"version",
337-
]);
338324
});
339325

340326
it("bypasses ClawHub trust checks for official skill install resolutions", async () => {
@@ -895,6 +881,12 @@ describe("skills-clawhub", () => {
895881
ownerHandle: "demo-owner",
896882
installedVersion: "1.0.0",
897883
});
884+
expect(reportClawHubSkillInstallTelemetryMock).toHaveBeenCalledWith({
885+
baseUrl: undefined,
886+
slug: "weather",
887+
ownerHandle: "demo-owner",
888+
version: "1.0.0",
889+
});
898890
});
899891

900892
it("does not require acknowledgement for owner-qualified clean skills missing only cards", async () => {

src/skills/lifecycle/clawhub.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -466,21 +466,6 @@ function buildDownloadedArtifactLock(
466466
};
467467
}
468468

469-
function buildInstallTelemetrySkills(
470-
skills: ClawHubSkillsLockfile["skills"],
471-
): Record<string, { version: string; installedAt: number; registry?: string }> {
472-
return Object.fromEntries(
473-
Object.entries(skills).map(([slug, entry]) => [
474-
slug,
475-
{
476-
version: entry.version,
477-
installedAt: entry.installedAt,
478-
...(entry.registry ? { registry: entry.registry } : {}),
479-
},
480-
]),
481-
);
482-
}
483-
484469
function snapshotClawHubSkillVerification(
485470
verification: ClawHubSkillVerificationResponse,
486471
): ClawHubSkillVerificationLock {
@@ -1508,8 +1493,9 @@ async function performClawHubSkillInstall(
15081493
await writeClawHubSkillsLockfile(params.workspaceDir, lock);
15091494
await reportClawHubSkillInstallTelemetry({
15101495
baseUrl: params.baseUrl,
1511-
root: params.workspaceDir,
1512-
skills: buildInstallTelemetrySkills(lock.skills),
1496+
slug: params.slug,
1497+
...(params.ownerHandle ? { ownerHandle: params.ownerHandle } : {}),
1498+
version,
15131499
}).catch(() => undefined);
15141500

15151501
return {

0 commit comments

Comments
 (0)