From 546aa5770a6ef274fcf8d5a1bf755486aeab01d6 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 4 Jun 2026 04:31:12 +0200 Subject: [PATCH] fix(e2e): report gauntlet log write failures --- CHANGELOG.md | 1 + scripts/check-plugin-gateway-gauntlet.mjs | 27 +++++++++++++------- test/scripts/plugin-gateway-gauntlet.test.ts | 22 ++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f592cfeb594b..6e0a212ecea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai - Release/CI/E2E: restore package changelog extraction after the post-2026.6.1 version bump, keep hydrated pnpm modules under `node_modules` for ARM/Linux package lifecycle scripts, keep OpenAI live-cache prerequisites advisory while Anthropic prerequisites stay blocking, retry Windows Parallels background log appends on transient file-lock errors, bound candidate GitHub and cross-OS Discord fetches, harden ARM smoke/browser checks, show Docker build heartbeats, reset Crabbox pnpm hydrate state, and isolate Testbox/Docker/release journey artifacts. - Release/CI/E2E: keep Crabbox hydrate pnpm stores on the persistent cache volume while still resetting volatile modules, reducing cold installs and runner memory churn. - Release/CI/E2E: fail secret-provider proof startup immediately when the gateway exits by signal instead of waiting for the readiness timeout. +- Release/CI/E2E: report plugin gateway gauntlet command-log write failures as failed rows instead of crashing the harness from child-process callbacks. ## 2026.6.1 diff --git a/scripts/check-plugin-gateway-gauntlet.mjs b/scripts/check-plugin-gateway-gauntlet.mjs index fcf9efb90ddd..687cdb418ec8 100644 --- a/scripts/check-plugin-gateway-gauntlet.mjs +++ b/scripts/check-plugin-gateway-gauntlet.mjs @@ -559,24 +559,33 @@ export function runMeasuredCommandLive(params) { ] .filter(Boolean) .join("\n"); - const diagnosticFailure = detectCommandDiagnosticFailure(stdout, finalStderr); - const logPath = writeCommandLog({ - logDir: params.logDir, - label: params.label, - command: [params.command, ...params.args], - stdout, - stderr: finalStderr, - }); + let logPath = null; + let logWriteError = null; + try { + logPath = writeCommandLog({ + logDir: params.logDir, + label: params.label, + command: [params.command, ...params.args], + stdout, + stderr: finalStderr, + }); + } catch (error) { + logWriteError = error instanceof Error ? error.message : String(error); + } + const outputDiagnosticFailure = detectCommandDiagnosticFailure(stdout, finalStderr); + const diagnosticFailure = + outputDiagnosticFailure ?? (logWriteError ? "command-log-write-failure" : null); resolve({ label: params.label, phase: params.phase, pluginId: params.pluginId ?? null, - status: finalStatus, + status: logWriteError ? 1 : finalStatus, diagnosticFailure, signal: signal ?? null, timedOut, spawnError, logPath, + ...(logWriteError ? { logWriteError } : {}), ...parseTimedMetrics(finalStderr, wallMs, mode), }); }; diff --git a/test/scripts/plugin-gateway-gauntlet.test.ts b/test/scripts/plugin-gateway-gauntlet.test.ts index d4fca852bfd6..e6d8ed763bb2 100644 --- a/test/scripts/plugin-gateway-gauntlet.test.ts +++ b/test/scripts/plugin-gateway-gauntlet.test.ts @@ -563,6 +563,28 @@ setInterval(() => {}, 1000); await expect(fs.readFile(row.logPath, "utf8")).resolves.toContain("live stderr"); }); + it("returns a failed row when measured command log writing fails", async () => { + const logDir = path.join(repoRoot, "not-a-directory"); + await fs.writeFile(logDir, "blocks log directory creation", "utf8"); + + const row = await runMeasuredCommandLive({ + cwd: repoRoot, + env: process.env, + logDir, + command: process.execPath, + args: ["-e", "console.log('live stdout')"], + label: "live-log-failure", + phase: "probe", + timeoutMs: 1000, + timeMode: "none", + }); + + expect(row.status).toBe(1); + expect(row.diagnosticFailure).toBe("command-log-write-failure"); + expect(row.logPath).toBeNull(); + expect(row.logWriteError).toMatch(/EEXIST|ENOTDIR|not a directory/u); + }); + it("cleans parent signal handlers after live measured commands settle", async () => { const logDir = path.join(repoRoot, "logs"); const before = process.listenerCount("SIGTERM");