fix(cli): give sandbox containers collision-proof names (#8880)

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 <verify@local>
This commit is contained in:
Shaojin Wen 2026-08-10 22:07:54 +08:00 committed by GitHub
parent 5dc98240c7
commit b2404836ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

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