From 5dc4cf602bc5e263e83cd16a12bb1e100544f4c3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 16 Aug 2026 16:18:18 -0700 Subject: [PATCH] fix(sandbox): surface list probe failures instead of empty results (#124881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sandboxListCommand and fetchAndFilterContainers swallowed backend and registry probe failures with catch(() => []). A broken Docker daemon or corrupt registry rendered as 'No sandbox runtimes found.' — and in recreate, filtered an entire fleet down to nothing — indistinguishable from a clean empty state. --json emitted a success envelope for a failed probe. Root cause: failure collapsed into the empty success shape at the consumer. The listers now propagate; the sandbox CLI runner already owns the error path (message + exit 1), and the new --json failure contract (6c66f48a7c1) emits the JSON error envelope. Regression: replaced the test that enshrined the masking ('handle errors gracefully' asserting the empty-state message) with the propagation contract — fails pre-fix. --- src/commands/sandbox.test.ts | 11 +++++++---- src/commands/sandbox.ts | 10 ++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/commands/sandbox.test.ts b/src/commands/sandbox.test.ts index bf8624b8ffaf..1cb22beac9db 100644 --- a/src/commands/sandbox.test.ts +++ b/src/commands/sandbox.test.ts @@ -169,12 +169,15 @@ describe("sandboxListCommand", () => { }); describe("error handling", () => { - it("should handle errors gracefully", async () => { + it("propagates backend probe failures instead of rendering an empty list", async () => { mocks.listSandboxContainers.mockRejectedValue(new Error("Docker not available")); - await sandboxListCommand({ browser: false, json: false }, runtime as never); - - expect(runtime.log).toHaveBeenCalledWith("No sandbox runtimes found."); + // A failing probe must reach the CLI error path (message + exit 1), + // not masquerade as "No sandbox runtimes found." + await expect( + sandboxListCommand({ browser: false, json: false }, runtime as never), + ).rejects.toThrow("Docker not available"); + expect(runtime.log).not.toHaveBeenCalledWith("No sandbox runtimes found."); }); }); }); diff --git a/src/commands/sandbox.ts b/src/commands/sandbox.ts index 8acc8451e153..4db48df268a2 100644 --- a/src/commands/sandbox.ts +++ b/src/commands/sandbox.ts @@ -51,8 +51,10 @@ export async function sandboxListCommand( opts: SandboxListOptions, runtime: RuntimeEnv, ): Promise { - const containers = opts.browser ? [] : await listSandboxContainers().catch(() => []); - const browsers = opts.browser ? await listSandboxBrowsers().catch(() => []) : []; + // A failing backend/registry probe must surface, not render as an empty + // list that reads as "no sandboxes". + const containers = opts.browser ? [] : await listSandboxContainers(); + const browsers = opts.browser ? await listSandboxBrowsers() : []; if (opts.json) { writeRuntimeJson(runtime, { containers, browsers }); @@ -121,8 +123,8 @@ function validateRecreateOptions(opts: SandboxRecreateOptions, runtime: RuntimeE } async function fetchAndFilterContainers(opts: SandboxRecreateOptions): Promise { - const allContainers = await listSandboxContainers().catch(() => []); - const allBrowsers = await listSandboxBrowsers().catch(() => []); + const allContainers = await listSandboxContainers(); + const allBrowsers = await listSandboxBrowsers(); let containers = opts.browser ? [] : allContainers; let browsers = opts.browser ? allBrowsers : [];