fix(sandbox): surface list probe failures instead of empty results (#124881)

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 (6c66f48a7c) 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.
This commit is contained in:
Peter Steinberger
2026-08-16 16:18:18 -07:00
committed by GitHub
parent b8a95e082b
commit 5dc4cf602b
2 changed files with 13 additions and 8 deletions
+7 -4
View File
@@ -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.");
});
});
});
+6 -4
View File
@@ -51,8 +51,10 @@ export async function sandboxListCommand(
opts: SandboxListOptions,
runtime: RuntimeEnv,
): Promise<void> {
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<FilteredContainers> {
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 : [];