fix(cli): keep approvals JSON failures parseable (#124585)

This commit is contained in:
Peter Steinberger
2026-08-16 06:35:35 -07:00
committed by GitHub
parent eafeeec537
commit b5491a08bf
3 changed files with 34 additions and 15 deletions
@@ -273,6 +273,19 @@ describe("exec approvals pending and resolve CLI", () => {
});
});
it("writes pending approval failures as JSON", async () => {
callGatewayFromCli.mockRejectedValue(new Error("gateway unavailable"));
await expect(runApprovalsCommand(["approvals", "pending", "--json"])).rejects.toThrow(
"__exit__:1",
);
expect(defaultRuntime.writeJson).toHaveBeenCalledOnce();
expect(defaultRuntime.writeJson).toHaveBeenCalledWith({ error: "gateway unavailable" }, 0);
expect(defaultRuntime.error).not.toHaveBeenCalled();
expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
});
it("preserves whitespace-bearing ids verbatim and keeps them distinct", async () => {
const now = Date.now();
callGatewayFromCli.mockImplementation(async (method: string) => {
+6 -3
View File
@@ -945,7 +945,8 @@ describe("exec approvals CLI", () => {
await expect(runNativeApprovalsFileCommand(filePath)).rejects.toThrow("__exit__:1");
expect(runtimeErrors[0]).toContain("File exceeds 1048576 bytes");
expect(writtenJson().error).toContain("File exceeds 1048576 bytes");
expect(runtimeErrors).toHaveLength(0);
expect(callGatewayFromCli).toHaveBeenCalledTimes(1);
});
@@ -954,7 +955,8 @@ describe("exec approvals CLI", () => {
await expect(runNativeApprovalsFileCommand(dir)).rejects.toThrow("__exit__:1");
expect(runtimeErrors[0]).toMatch(/EISDIR|directory/i);
expect(writtenJson().error).toMatch(/EISDIR|directory/i);
expect(runtimeErrors).toHaveLength(0);
expect(callGatewayFromCli).toHaveBeenCalledTimes(1);
});
@@ -990,7 +992,8 @@ describe("exec approvals CLI", () => {
openSpy.mockRestore();
}
expect(runtimeErrors[0]).toContain("File exceeds 1048576 bytes");
expect(writtenJson().error).toContain("File exceeds 1048576 bytes");
expect(runtimeErrors).toHaveLength(0);
expect(callGatewayFromCli).toHaveBeenCalledTimes(1);
});
});
+15 -12
View File
@@ -302,8 +302,6 @@ async function loadSnapshotTarget(opts: ExecApprovalsCliOpts): Promise<{
}
function exitWithError(message: string): never {
defaultRuntime.error(message);
defaultRuntime.exit(1);
throw new Error(message);
}
@@ -382,6 +380,16 @@ function formatCliError(err: unknown): string {
return safe.length > 300 ? `${truncateUtf16Safe(safe, 300)}...` : safe;
}
function failApprovalsCommand(err: unknown, opts: ExecApprovalsCliOpts): void {
const message = formatCliError(err);
if (opts.json) {
defaultRuntime.writeJson({ error: message }, 0);
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
}
function isApprovalDecision(value: string): value is ApprovalDecision {
return (APPROVAL_DECISIONS as readonly string[]).includes(value);
}
@@ -1082,8 +1090,7 @@ async function runAllowlistMutation(
targetLabel: context.targetLabel,
});
} catch (err) {
defaultRuntime.error(formatCliError(err));
defaultRuntime.exit(1);
failApprovalsCommand(err, opts);
}
}
@@ -1132,8 +1139,7 @@ export function registerExecApprovalsCli(program: Command) {
}
renderPendingApprovals(entries);
} catch (err) {
defaultRuntime.error(formatCliError(err));
defaultRuntime.exit(1);
failApprovalsCommand(err, opts);
}
});
nodesCallOpts(pendingCmd);
@@ -1146,8 +1152,7 @@ export function registerExecApprovalsCli(program: Command) {
try {
await resolvePendingApproval(id, decision, opts);
} catch (err) {
defaultRuntime.error(formatCliError(err));
defaultRuntime.exit(1);
failApprovalsCommand(err, opts);
}
});
nodesCallOpts(resolveCmd);
@@ -1187,8 +1192,7 @@ export function registerExecApprovalsCli(program: Command) {
renderApprovalsSnapshot(snapshot, targetLabel);
renderEffectivePolicy({ report: effectivePolicy });
} catch (err) {
defaultRuntime.error(formatCliError(err));
defaultRuntime.exit(1);
failApprovalsCommand(err, opts);
}
});
nodesCallOpts(getCmd, { timeoutMs: APPROVALS_GET_DEFAULT_TIMEOUT_MS });
@@ -1236,8 +1240,7 @@ export function registerExecApprovalsCli(program: Command) {
file.version = 1;
await saveSnapshotTargeted({ opts, source, nodeId, file, baseHash, targetLabel });
} catch (err) {
defaultRuntime.error(formatCliError(err));
defaultRuntime.exit(1);
failApprovalsCommand(err, opts);
}
});
nodesCallOpts(setCmd);