fix: send owner-qualified install telemetry (#102377)

This commit is contained in:
Patrick Erichsen
2026-07-08 20:18:09 -07:00
committed by GitHub
parent e8bd3ae26a
commit 1252a3da59
4 changed files with 42 additions and 84 deletions
+20 -18
View File
@@ -108,20 +108,23 @@ describe("openclaw skills install ClawHub GitHub-backed E2E", () => {
const registry = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-clawhub-cli-e2e-"));
try {
const result = await spawnOpenClaw(["skills", "install", "aiq-deploy", "--global"], {
cwd: process.cwd(),
env: {
...process.env,
OPENCLAW_STATE_DIR: stateDir,
OPENCLAW_CONFIG_PATH: path.join(stateDir, "openclaw.json"),
OPENCLAW_CLAWHUB_URL: registry,
OPENCLAW_CLAWHUB_TOKEN: "test-token",
OPENCLAW_CLAWHUB_GITHUB_CODELOAD_BASE_URL: registry,
CLAWHUB_DISABLE_TELEMETRY: "",
CLAWDHUB_DISABLE_TELEMETRY: "",
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
const result = await spawnOpenClaw(
["skills", "install", "@demo-owner/aiq-deploy", "--global"],
{
cwd: process.cwd(),
env: {
...process.env,
OPENCLAW_STATE_DIR: stateDir,
OPENCLAW_CONFIG_PATH: path.join(stateDir, "openclaw.json"),
OPENCLAW_CLAWHUB_URL: registry,
OPENCLAW_CLAWHUB_TOKEN: "test-token",
OPENCLAW_CLAWHUB_GITHUB_CODELOAD_BASE_URL: registry,
CLAWHUB_DISABLE_TELEMETRY: "",
CLAWDHUB_DISABLE_TELEMETRY: "",
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
},
},
});
);
expect(result.status, result.stderr || result.stdout).toBe(0);
await expect(
@@ -137,11 +140,10 @@ describe("openclaw skills install ClawHub GitHub-backed E2E", () => {
throw new Error(`Expected one install telemetry request, saw: ${requestLog.join(", ")}`);
}
expect(telemetryBodies[0]).toMatchObject({
roots: [
{
skills: [{ slug: "aiq-deploy", version: commit }],
},
],
event: "install",
slug: "aiq-deploy",
ownerHandle: "demo-owner",
version: commit,
});
} finally {
await new Promise<void>((resolve) => {
+11 -33
View File
@@ -404,10 +404,6 @@ export type ClawHubDownloadResult = {
cleanup: () => Promise<void>;
};
export type ClawHubInstallTelemetrySkill = {
version?: string | null;
};
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
type ClawHubRequestParams = {
@@ -1619,8 +1615,9 @@ export async function downloadClawHubGitHubSkillArchive(params: {
export async function reportClawHubSkillInstallTelemetry(params: {
baseUrl?: string;
token?: string;
root: string;
skills: Record<string, ClawHubInstallTelemetrySkill>;
slug: string;
ownerHandle?: string;
version?: string | null;
timeoutMs?: number;
fetchImpl?: FetchLike;
}): Promise<void> {
@@ -1628,12 +1625,10 @@ export async function reportClawHubSkillInstallTelemetry(params: {
if (!token || isClawHubTelemetryDisabled()) {
return;
}
const skills = Object.entries(params.skills)
.map(([slug, entry]) => ({
slug,
version: entry.version ?? null,
}))
.filter((entry) => entry.slug.length > 0);
const slug = params.slug.trim();
if (!slug) {
return;
}
const { response, url, hasToken } = await clawhubRequest({
baseUrl: params.baseUrl,
@@ -1643,13 +1638,10 @@ export async function reportClawHubSkillInstallTelemetry(params: {
timeoutMs: params.timeoutMs,
fetchImpl: params.fetchImpl,
json: {
roots: [
{
rootId: digestSha256Hex(path.resolve(params.root)),
label: formatTelemetryRootLabel(params.root),
skills,
},
],
event: "install",
slug,
...(params.ownerHandle ? { ownerHandle: params.ownerHandle } : {}),
version: params.version ?? undefined,
},
});
if (!response.ok) {
@@ -1665,20 +1657,6 @@ function isClawHubTelemetryDisabled(): boolean {
return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
}
function formatTelemetryRootLabel(root: string): string {
const home = os.homedir();
const absolute = path.resolve(root);
if (absolute === home) {
return "~";
}
const normalized = absolute.replaceAll("\\", "/");
const normalizedHome = home.replaceAll("\\", "/");
const withinHome = normalized.startsWith(`${normalizedHome}/`);
const stripped = withinHome ? normalized.slice(normalizedHome.length + 1) : normalized;
const tail = stripped.split("/").filter(Boolean).slice(-2).join("/");
return withinHome ? `~/${tail}` : tail || absolute;
}
/** Resolves the preferred latest package version from detail metadata. */
export function resolveLatestVersionFromPackage(detail: ClawHubPackageDetail): string | null {
return detail.package?.latestVersion ?? detail.package?.tags?.latest ?? null;
+8 -16
View File
@@ -318,23 +318,9 @@ describe("skills-clawhub", () => {
expect(archiveCleanupMock).toHaveBeenCalledTimes(1);
expect(reportClawHubSkillInstallTelemetryMock).toHaveBeenCalledWith({
baseUrl: undefined,
root: "/tmp/workspace",
skills: expect.objectContaining({
agentreceipt: expect.objectContaining({
version: "1.0.0",
installedAt: expect.any(Number),
registry: "https://clawhub.ai",
}),
}),
slug: "agentreceipt",
version: "1.0.0",
});
const telemetrySkills = reportClawHubSkillInstallTelemetryMock.mock.calls[0]?.[0]?.skills as
| Record<string, Record<string, unknown>>
| undefined;
expect(Object.keys(telemetrySkills?.agentreceipt ?? {}).toSorted()).toEqual([
"installedAt",
"registry",
"version",
]);
});
it("bypasses ClawHub trust checks for official skill install resolutions", async () => {
@@ -895,6 +881,12 @@ describe("skills-clawhub", () => {
ownerHandle: "demo-owner",
installedVersion: "1.0.0",
});
expect(reportClawHubSkillInstallTelemetryMock).toHaveBeenCalledWith({
baseUrl: undefined,
slug: "weather",
ownerHandle: "demo-owner",
version: "1.0.0",
});
});
it("does not require acknowledgement for owner-qualified clean skills missing only cards", async () => {
+3 -17
View File
@@ -466,21 +466,6 @@ function buildDownloadedArtifactLock(
};
}
function buildInstallTelemetrySkills(
skills: ClawHubSkillsLockfile["skills"],
): Record<string, { version: string; installedAt: number; registry?: string }> {
return Object.fromEntries(
Object.entries(skills).map(([slug, entry]) => [
slug,
{
version: entry.version,
installedAt: entry.installedAt,
...(entry.registry ? { registry: entry.registry } : {}),
},
]),
);
}
function snapshotClawHubSkillVerification(
verification: ClawHubSkillVerificationResponse,
): ClawHubSkillVerificationLock {
@@ -1508,8 +1493,9 @@ async function performClawHubSkillInstall(
await writeClawHubSkillsLockfile(params.workspaceDir, lock);
await reportClawHubSkillInstallTelemetry({
baseUrl: params.baseUrl,
root: params.workspaceDir,
skills: buildInstallTelemetrySkills(lock.skills),
slug: params.slug,
...(params.ownerHandle ? { ownerHandle: params.ownerHandle } : {}),
version,
}).catch(() => undefined);
return {