fix(cli): render Gateway health JSON failures (#129106)

This commit is contained in:
Peter Steinberger
2026-08-25 01:04:03 -07:00
committed by GitHub
parent 203024eb33
commit 1901ddaa81
3 changed files with 63 additions and 21 deletions
+18 -16
View File
@@ -80,29 +80,31 @@ describe("runGatewayHealthJsonRoute", () => {
);
});
it("formats local config resolution failures", async () => {
it("leaves local config resolution failures to the root CLI renderer", async () => {
const runtime = createRuntime();
const error = new Error("config unavailable");
const callGateway = vi.fn();
await runGatewayHealthJsonRoute(
{
rpc: { json: true, timeout: "10000" },
localPortOverride: 19083,
},
runtime as never,
{
callGateway,
readNonObservingHealthConfig: vi.fn(async () => {
throw error;
}),
},
);
await expect(
runGatewayHealthJsonRoute(
{
rpc: { json: true, timeout: "10000" },
localPortOverride: 19083,
},
runtime as never,
{
callGateway,
readNonObservingHealthConfig: vi.fn(async () => {
throw error;
}),
},
),
).rejects.toBe(error);
expect(callGateway).not.toHaveBeenCalled();
expect(runtime.writeJson).not.toHaveBeenCalled();
expect(runtime.error).toHaveBeenCalledWith(error.message);
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(runtime.error).not.toHaveBeenCalled();
expect(runtime.exit).not.toHaveBeenCalled();
});
it("preserves structured transport errors", async () => {
+1 -4
View File
@@ -1,5 +1,4 @@
// Route-first machine-readable Gateway health command.
import { formatErrorMessage } from "../../infra/errors.js";
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
type GatewayHealthRpcOpts = Parameters<
@@ -65,9 +64,7 @@ export async function runGatewayHealthJsonRoute(
);
} catch (error) {
if (!rpc) {
runtime.error(formatErrorMessage(error));
runtime.exit(1);
return;
throw error;
}
const [healthModule, callModule] = await Promise.all([
deps.emitReachableGatewayAuthDiagnostic && deps.readNonObservingHealthConfig
+44 -1
View File
@@ -211,6 +211,33 @@ describe("cli json stdout contract", () => {
message: "Use --agent or --all-agents, not both",
tty: true,
},
{
name: "routed Gateway health config failure in JSON mode",
args: ["gateway", "health", "--port", "29793", "--json"],
message: "AUTOQA_ROUTE_CONFIG_READ_FAILURE",
configReadFailure: true,
},
{
name: "Gateway health config failure in human mode",
args: ["gateway", "health", "--port", "29793"],
message: "AUTOQA_ROUTE_CONFIG_READ_FAILURE",
configReadFailure: true,
human: true,
},
{
name: "Gateway health config failure through forced Commander",
args: ["gateway", "health", "--port", "29793", "--json"],
message: "AUTOQA_ROUTE_CONFIG_READ_FAILURE",
configReadFailure: true,
commander: true,
},
{
name: "routed Gateway health config failure with dual TTYs",
args: ["gateway", "health", "--port", "29793", "--json"],
message: "AUTOQA_ROUTE_CONFIG_READ_FAILURE",
configReadFailure: true,
tty: true,
},
{
name: "specialized explicit Gateway authentication failure",
args: ["gateway", "call", "system-presence", "--url", "ws://127.0.0.1:29793", "--json"],
@@ -226,6 +253,18 @@ describe("cli json stdout contract", () => {
[
'import net from "node:net";',
`net.Socket.prototype.connect = function () { throw new Error(${JSON.stringify(gatewayError)}); };`,
...("configReadFailure" in testCase
? [
'import fs from "node:fs";',
"const originalExistsSync = fs.existsSync;",
"fs.existsSync = function (target, ...args) {",
' if (String(target) === process.env.OPENCLAW_CONFIG_PATH && new Error().stack?.includes("readNonObservingHealthConfig")) {',
` throw new Error(${JSON.stringify(testCase.message)});`,
" }",
" return originalExistsSync.call(this, target, ...args);",
"};",
]
: []),
...("tty" in testCase
? [
'Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true });',
@@ -269,7 +308,11 @@ describe("cli json stdout contract", () => {
ok: false,
error: { type: "cli_error", message: testCase.message },
});
expect(result.stderr).not.toContain(testCase.message);
if ("configReadFailure" in testCase && !("commander" in testCase)) {
expect(result.stderr).toContain(testCase.message);
} else {
expect(result.stderr).not.toContain(testCase.message);
}
}
if ("tty" in testCase) {
expect(result.stderr).toContain("\u001B[?25h");