Recreate a computer whose Docker network was removed (#226)

When a stack restart or network prune removes a bot's network, its
stopped computer keeps pointing at the old network id. Starting it fails
with "network ... not found", the supervisor returned 500 on every
provision, and runs for that bot retried setup forever while staying
queued. The supervisor now recognizes that error and recreates the
container on a fresh network with the same home, as it already does for
an outdated image.


Claude-Session: https://claude.ai/code/session_01QAiXRe1FLMAJT9jkvwFZFq

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cynarAI 2026-10-23 05:23:00 +02:00 committed by GitHub
parent ea7892baa3
commit 679b90a667
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 39 additions and 6 deletions

View File

@ -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);
});
});

View File

@ -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,
});
}
}
}

View File

@ -83,6 +83,11 @@ export async function preferComputerControl<T>(
}
}
/** 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);