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

  1. Check the computer object actually has a `container` string property before destroying.
  2. Use the computer object exactly as returned by spawnDockerComputer — don't hand-edit the container name.
  3. If the record is corrupt/stale, remove its registry entry instead of calling destroy (per the code's own comment).
  4. 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

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


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)