From b2404836ba57772f4bcc10440b2aed2565007cd1 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 10 Aug 2026 22:07:54 +0800 Subject: [PATCH] fix(cli): give sandbox containers collision-proof names (#8880) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The regular-run container name was allocated by counting docker ps -a and taking the first free index — a check-then-run window with nothing holding the name between the count and `docker run --name`. That was survivable while launches on a shared docker daemon were rare, but the CI pool packs several runner registrations per host, and a dense autofix scan launches sandboxes on sibling registrations within the same second: in run 31389561905, SEVEN of fourteen legs lost the race — each got `docker: Conflict. The container name "/qwen-code-0.21.8-0" is already in use` (exit 125) one second into its agent step, burned a round, and pushed items toward their round caps. A random 8-hex suffix replaces the counter, the same scheme the integration-test branch has always used. Nothing predicts the name: run-agent.mjs parses it from the `ContainerName (regular):` line, and the stale-container reaper matches on the image-name prefix, which the suffix preserves. The docker ps call goes with the counter — one less execSync on the launch path. Co-authored-by: verify --- packages/cli/src/utils/sandbox.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts index 86b76119f6..706ef1dec4 100644 --- a/packages/cli/src/utils/sandbox.ts +++ b/packages/cli/src/utils/sandbox.ts @@ -637,16 +637,16 @@ export async function start_sandbox( )}`; writeStderrLine(`ContainerName: ${containerName}`); } else { - let index = 0; - const containerNameCheck = execSync( - `${config.command} ps -a --format "{{.Names}}"`, - ) - .toString() - .trim(); - while (containerNameCheck.includes(`${imageName}-${index}`)) { - index++; - } - containerName = `${imageName}-${index}`; + // Random suffix, NOT a counted index: several runner registrations can + // share one docker daemon (the CI pool packs multiple runners per + // host), and the old count-then-run window let concurrent launches + // pick the same index — docker then rejects the loser's `docker run` + // with a name Conflict (exit 125). Observed at scale: 7 of 14 legs of + // one autofix scan lost that race in a single tick. Consumers that + // need the name parse it from the line below rather than predicting + // it, and cleanup tooling matches on the image-name prefix, so the + // suffix shape is free to be collision-proof. + containerName = `${imageName}-${randomBytes(4).toString('hex')}`; writeStderrLine(`ContainerName (regular): ${containerName}`); } args.push('--name', containerName, '--hostname', containerName);