diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b8819c90490..b61fdb2bc0f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Docs: https://docs.openclaw.ai - Release/CI/E2E: write package Telegram Docker artifacts to unique per-run directories by default so parallel live/RTT runs cannot overwrite evidence. - Release/CI/E2E: fail secret-provider proof runs when temporary state cleanup still fails after retries instead of hiding the cleanup error. - Release/CI/E2E: fail package-candidate ref proofs when temporary source worktree cleanup fails instead of leaving stale worktrees behind. +- Release/CI/E2E: remove package tarball extract directories when tar extraction fails before validation can continue. - Release/CI/E2E: retry generated temp-state cleanup after removal failures and route plugin lifecycle measurement edits to their owner tests. - Release/CI/E2E: close parent gateway log handles after spawning RPC RTT probes so repeated measurements do not leak file descriptors. - Release/CI/E2E: fail RPC RTT probes when temporary state cleanup fails instead of hiding leftover scratch directories. diff --git a/scripts/check-openclaw-package-tarball.mjs b/scripts/check-openclaw-package-tarball.mjs index 68df8aeefc0c..7ff38cb25aad 100644 --- a/scripts/check-openclaw-package-tarball.mjs +++ b/scripts/check-openclaw-package-tarball.mjs @@ -63,6 +63,7 @@ try { }), ); if (extract.status !== 0) { + fs.rmSync(extractDir, { recursive: true, force: true }); fail(`tar -xf failed for ${tarball}: ${extract.stderr || extract.status}`); } } catch (error) { diff --git a/test/scripts/check-openclaw-package-tarball.test.ts b/test/scripts/check-openclaw-package-tarball.test.ts index ae0b41721442..2e154730f614 100644 --- a/test/scripts/check-openclaw-package-tarball.test.ts +++ b/test/scripts/check-openclaw-package-tarball.test.ts @@ -1,7 +1,15 @@ import { spawnSync } from "node:child_process"; -import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs"; +import { + chmodSync, + existsSync, + mkdtempSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; -import { dirname, join } from "node:path"; +import { delimiter, dirname, join } from "node:path"; import { describe, expect, it } from "vitest"; import { LOCAL_BUILD_METADATA_DIST_PATHS } from "../../scripts/lib/local-build-metadata-paths.mjs"; @@ -67,6 +75,47 @@ function withTarball( } describe("check-openclaw-package-tarball", () => { + it.runIf(process.platform !== "win32")("removes the extract dir when tar extraction fails", () => { + const root = mkdtempSync(join(tmpdir(), "openclaw-package-tarball-extract-fail-")); + try { + const fakeBin = join(root, "bin"); + mkdirSync(fakeBin); + const extractDirFile = join(root, "extract-dir.txt"); + const fakeTar = join(fakeBin, "tar"); + writeFileSync( + fakeTar, + [ + "#!/usr/bin/env node", + "const fs = require('node:fs');", + "const args = process.argv.slice(2);", + "if (args[0] === '-tf') { console.log('package/package.json'); process.exit(0); }", + "const outputDir = args[args.indexOf('-C') + 1];", + "fs.writeFileSync(process.env.OPENCLAW_TEST_EXTRACT_DIR_FILE, outputDir);", + "console.error('extract denied');", + "process.exit(7);", + ].join("\n"), + ); + chmodSync(fakeTar, 0o755); + const tarball = join(root, "openclaw.tgz"); + writeFileSync(tarball, "not used by fake tar"); + + const result = spawnSync("node", [CHECK_SCRIPT, tarball], { + encoding: "utf8", + env: { + ...process.env, + OPENCLAW_TEST_EXTRACT_DIR_FILE: extractDirFile, + PATH: `${fakeBin}${delimiter}${process.env.PATH ?? ""}`, + }, + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain("extract denied"); + expect(existsSync(readFileSync(extractDirFile, "utf8"))).toBe(false); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + it("allows legacy private QA inventory entries omitted from shipped tarballs through 2026.4.25", () => { withTarball( ["dist/index.js", "dist/extensions/qa-channel/runtime-api.js"],