Hmbown/CodeWhale · error · SpawnError
invalid_container
invalid_container
Error message
docker computer has no valid container name
What it means
Thrown by destroyDockerComputer() when the computer object's container field is missing or fails CONTAINER_RE validation. The library only removes containers whose names match its own spawn naming scheme, as a safety guard against destroying arbitrary containers. This indicates a corrupted or hand-built computer record rather than a docker problem.
Solutions
- Check the computer object actually has a `container` string property before destroying.
- Use the computer object exactly as returned by spawnDockerComputer — don't hand-edit the container name.
- If the record is corrupt/stale, remove its registry entry instead of calling destroy (per the code's own comment).
- Confirm the container name matches the plugin's scheme (`cu-spawn-<id>-<hex>`); if it was renamed manually, destroy it with plain `docker rm -f` yourself.
Example fix
// before
await destroyDockerComputer({ container: "my_desktop" }); // fails CONTAINER_RE
// after
const computer = await spawnDockerComputer();
await destroyDockerComputer(computer); // container name from spawn, matches pattern Defensive patterns
Strategy: type-guard
Validate before calling
const CONTAINER_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
function isDestroyable(computer) {
return typeof computer?.container === "string" && CONTAINER_RE.test(computer.container);
} Type guard
function isSpawnedComputer(c) {
return typeof c === "object" && c !== null &&
typeof c.container === "string" && /^cu-spawn-.+$/.test(c.container);
} Try / catch
try {
await destroyDockerComputer(computer);
} catch (e) {
if (e?.code === "invalid_container") {
// the record is corrupt; drop its registry entry instead
}
} Prevention
- Always pass the computer object returned by spawnDockerComputer unchanged.
- Never hand-edit or truncate the container field when persisting records.
- Validate computer records deserialized from disk/registry before destroying.
- If a container was renamed manually, remove it with plain `docker rm -f`.
When it happens
Trigger: destroyDockerComputer(computer) called with undefined/null computer, computer.container missing, or a container name that does not match the plugin's expected pattern (CONTAINER_RE) — e.g. a name from an older plugin version or fabricated by the caller.
Common situations: Passing a plain string instead of a computer object, persisting the computer record with a truncated/renamed container field, mixing records from a different tool version, or a stale registry entry after manual `docker rename`.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Choose approve or decline
- install source must not be empty
- strategy must be auto, a11y, app or event
- 1
- A pinned task provider requires an explicit model
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f30f4e0fc131ec46.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/spawn.mjs:126
if (Date.now() >= deadline) break;
await new Promise((resolve) => setTimeout(resolve, 500));
}
throw new SpawnError("spawn_failed", `spawned desktop did not come up: ${lastErr}`);
} catch (err) {
await cleanup();
throw err;
}
}
/**
* Destroy a spawned container — but only one this plugin created. A docker
* computer whose container lacks our spawn label is left running and reported
* not_spawned; removing its registry entry is still the caller's choice.
*/
export async function destroyDockerComputer(computer) {
const container = computer?.container;
if (!container || !CONTAINER_RE.test(container)) {
throw new SpawnError("invalid_container", "docker computer has no valid container name");
}
const insp = await docker(["container", "inspect", "--format", `{{index .Config.Labels "${SPAWN_LABEL}"}}`, container], { timeoutMs: 10_000, signal: null });
if (insp.code !== 0) return { destroyed: false, reason: "container_gone" };
if (insp.stdout.trim() !== "1") return { destroyed: false, reason: "not_spawned" };
const r = await docker(["rm", "-f", container], { timeoutMs: 20_000, signal: null });
if (r.code !== 0) throw new SpawnError("cleanup_failed", `docker rm -f ${container} failed: ${trim(r.stderr)}`, r);
return { destroyed: true };
}
/**
* Session teardown: destroy every spawned container this MCP process owns.
* Other sessions' spawns are left alone — the registry is shared but a
* container belongs to the process that created it.
*/
export async function destroySessionSpawns() {
const listed = await docker(["ps", "-aq", "--filter", `label=${SPAWN_LABEL}=1`, "--filter", `label=${SESSION_LABEL}=${SESSION_ID}`], { timeoutMs: 10_000, signal: null });
if (listed.code !== 0) return { destroyed: [], error: trim(listed.stderr) };
const ids = listed.stdout.split("\n").map((s) => s.trim()).filter(Boolean);View on GitHub (pinned to 73e0f67d83)