fix: plugin publishing no longer fails on invalid temp paths (#3344)

* fix: keep plugin inspector workspaces writable

* test: cover inspector temp fallback by platform

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Momo
2026-07-31 16:55:56 +08:00
committed by GitHub
co-authored by Vincent Koc
parent 4187c7dd1c
commit e9316c1c7d
2 changed files with 84 additions and 8 deletions
+54 -1
View File
@@ -1,12 +1,65 @@
/* @vitest-environment node */
import { describe, expect, it } from "vitest";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
buildPublishInspectorRunCheckOptions,
createPackageInspectorWorkspace,
normalizeInspectorReportForPublish,
} from "./packageInspectorNode";
const originalPlatform = process.platform;
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform });
});
describe("package inspector publish normalization", () => {
it("falls back to /tmp when the configured temp directory is unavailable", async () => {
Object.defineProperty(process, "platform", { value: "linux" });
const attemptedPrefixes: string[] = [];
const createTempDir = async (prefix: string) => {
attemptedPrefixes.push(prefix);
if (attemptedPrefixes.length === 1) {
throw Object.assign(new Error("configured temp directory is unavailable"), {
code: "ENOENT",
});
}
return `${prefix}workspace`;
};
await expect(
createPackageInspectorWorkspace("/home/sbx_user1051", createTempDir),
).resolves.toBe(path.join("/tmp", "clawhub-plugin-inspector-workspace"));
expect(attemptedPrefixes).toEqual([
path.join("/home/sbx_user1051", "clawhub-plugin-inspector-"),
path.join("/tmp", "clawhub-plugin-inspector-"),
]);
});
it("does not use the POSIX fallback on Windows", async () => {
Object.defineProperty(process, "platform", { value: "win32" });
const error = Object.assign(new Error("configured temp directory is unavailable"), {
code: "ENOENT",
});
const createTempDir = async () => {
throw error;
};
await expect(createPackageInspectorWorkspace("C:\\Temp", createTempDir)).rejects.toBe(error);
});
it("does not hide unrelated workspace creation errors", async () => {
const error = Object.assign(new Error("temporary storage failed"), { code: "EIO" });
const createTempDir = async () => {
throw error;
};
await expect(createPackageInspectorWorkspace("/home/sbx_user1051", createTempDir)).rejects.toBe(
error,
);
});
it("targets latest stable OpenClaw for publish-time inspection", () => {
expect(buildPublishInspectorRunCheckOptions("/tmp/plugin", "2026-07-30T00:00:00.000Z")).toEqual(
expect.objectContaining({
+30 -7
View File
@@ -1,6 +1,6 @@
"use node";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { v } from "convex/values";
@@ -42,6 +42,9 @@ type InspectorReport = {
};
const AUTHOR_REMEDIATION_DOCS_BASE = "https://docs.openclaw.ai/clawhub/plugin-validation-fixes";
const PACKAGE_INSPECTOR_TEMP_PREFIX = "clawhub-plugin-inspector-";
const SERVERLESS_TEMP_DIR = "/tmp";
const TEMP_DIR_FALLBACK_ERROR_CODES = new Set(["EACCES", "ENOENT", "ENOTDIR", "EPERM", "EROFS"]);
const LEGACY_AUTHOR_REMEDIATION_SUMMARIES = {
"channel-env-vars":
@@ -144,6 +147,27 @@ export function buildPublishInspectorRunCheckOptions(root: string, generatedAt:
};
}
export async function createPackageInspectorWorkspace(
preferredTempDir = tmpdir(),
createTempDir: (prefix: string) => Promise<string> = mkdtemp,
) {
try {
return await createTempDir(path.join(preferredTempDir, PACKAGE_INSPECTOR_TEMP_PREFIX));
} catch (error) {
const code =
typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
if (
process.platform === "win32" ||
path.resolve(preferredTempDir) === SERVERLESS_TEMP_DIR ||
typeof code !== "string" ||
!TEMP_DIR_FALLBACK_ERROR_CODES.has(code)
) {
throw error;
}
return await createTempDir(path.join(SERVERLESS_TEMP_DIR, PACKAGE_INSPECTOR_TEMP_PREFIX));
}
}
export const runPackageInspectorForPublishInternal = internalAction({
args: {
packageName: v.string(),
@@ -163,12 +187,9 @@ export const runPackageInspectorForPublishInternal = internalAction({
metadata: inspectorMetadataValidator,
}),
handler: async (ctx, args) => {
const root = path.join(
tmpdir(),
`clawhub-plugin-inspector-${Date.now()}-${Math.random().toString(16).slice(2)}`,
);
let root: string | undefined;
try {
await mkdir(root, { recursive: true });
root = await createPackageInspectorWorkspace();
for (const file of args.files) {
const blob = await ctx.storage.get(file.storageId);
if (!blob) {
@@ -209,7 +230,9 @@ export const runPackageInspectorForPublishInternal = internalAction({
},
};
} finally {
await rm(root, { recursive: true, force: true });
if (root) {
await rm(root, { recursive: true, force: true });
}
}
},
});