diff --git a/infra/sandboxes/supervisor/src/index.test.ts b/infra/sandboxes/supervisor/src/index.test.ts index 8bdb03d..fff3b4e 100644 --- a/infra/sandboxes/supervisor/src/index.test.ts +++ b/infra/sandboxes/supervisor/src/index.test.ts @@ -30,6 +30,7 @@ import { hasValidBearerToken, interactiveScreenCommand, isComputerControlUnavailable, + isMissingNetworkError, nextScreenIndex, normalizeWorkspaceRelative, parseObservation, @@ -846,3 +847,17 @@ describe("docker exec stream demux", () => { }); }); }); + +describe("isMissingNetworkError", () => { + it("recognizes Docker refusing to start a container whose network is gone", () => { + expect( + isMissingNetworkError( + new Error( + "(HTTP code 404) no such container - failed to set up container networking: network 065709cd6047a1b2 not found", + ), + ), + ).toBe(true); + expect(isMissingNetworkError(new Error("(HTTP code 404) no such container"))).toBe(false); + expect(isMissingNetworkError(new Error("image not found"))).toBe(false); + }); +}); diff --git a/infra/sandboxes/supervisor/src/index.ts b/infra/sandboxes/supervisor/src/index.ts index 598efc2..e81e9a7 100644 --- a/infra/sandboxes/supervisor/src/index.ts +++ b/infra/sandboxes/supervisor/src/index.ts @@ -71,6 +71,7 @@ import { hasValidBearerToken, interactiveScreenCommand, isComputerControlUnavailable, + isMissingNetworkError, nextScreenIndex, normalizeWorkspaceRelative, parseObservation, @@ -287,12 +288,24 @@ app.post("/computers", async (c) => { (!brokerSocketDir || githubBrokerSocketMounted(info.Mounts)) && extraBindsMounted(info.Mounts, resolveExtraBinds()) ) { - if (!info.State.Running) await existing.start(); - return c.json({ - id: existing.id, - image: COMPUTER_IMAGE, - resumed: true, - }); + // A container whose network was removed (a stack restart or network prune) + // never starts again; recreate it below on a fresh network with the same home. + const running = + info.State.Running || + (await existing.start().then( + () => true, + (error: unknown) => { + if (isMissingNetworkError(error)) return false; + throw error; + }, + )); + if (running) { + return c.json({ + id: existing.id, + image: COMPUTER_IMAGE, + resumed: true, + }); + } } } diff --git a/infra/sandboxes/supervisor/src/supervisor-logic.ts b/infra/sandboxes/supervisor/src/supervisor-logic.ts index 58962b5..fa469e1 100644 --- a/infra/sandboxes/supervisor/src/supervisor-logic.ts +++ b/infra/sandboxes/supervisor/src/supervisor-logic.ts @@ -83,6 +83,11 @@ export async function preferComputerControl( } } +/** Docker cannot start a container whose network was removed underneath it. */ +export function isMissingNetworkError(error: unknown) { + return /network \S+ not found/i.test(error instanceof Error ? error.message : String(error)); +} + export class ComputerControlUnavailableError extends Error { constructor(message = "computer control unavailable") { super(message);