Hmbown/CodeWhale · error · ServerError
unsupported_on_transport
unsupported_on_transport
Error message
app_script runs on the local computer only — the ${computer.transport} transport stays a computer-use channel, never a shell What it means
app_script executes local application scripting and is deliberately restricted to computers with the local transport. On ssh/docker (or any remote transport) the server refuses before dispatching, so a remote computer-use channel can never be used as a shell. Remote agents enforce the same gate, so there is no hop that would honor it.
Solutions
- Run app_script against a computer whose transport is "local"
- For remote machines use the proper remote tools (screenshot, type, key, etc.) instead of scripting
- If you need remote shell access, use a dedicated SSH/shell mechanism, not the computer-use channel
- Add/configure a local computer entry if local scripting is genuinely required
Example fix
// before
await appScript({ computer: "ssh-box", script: "ls" })
// after
await appScript({ computer: "local", script: "ls" }) Defensive patterns
Strategy: validation
Validate before calling
if (computer.transport !== "local") throw new Error("app_script is local-only; use remote computer-use tools for this transport"); Type guard
const isLocal = (c) => c?.transport === "local";
Try / catch
try { await appScript({ computer, script }) } catch (e) { if (e.code === "unsupported_on_transport") { /* fall back to remote tools or select a local computer */ } else throw e; } Prevention
- Gate scripting features on computer.transport === "local" in your tool-selection logic
- Never treat computer-use channels as remote shells
- Document per-transport tool availability in automation plans
When it happens
Trigger: Calling app_script while the bound computer's transport is not "local" — e.g. an ssh or docker computer where the tool was expected to run commands remotely.
Common situations: Using computer-use against an ssh machine and trying to run scripts; assuming app_script is a general remote-execution tool; environments where only remote computers are configured.
Related errors
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7b54b2eaabe21f9d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:912
const focused = await callTool({ name: "focus", arguments: { target: args.target, computer: computer.id } });
const focusBody = JSON.parse(focused.content[0].text);
// For a chord the element's window is what matters — key equivalents
// dispatch at window level, so a focus refusal must not block delivery.
// Text is different: characters go to the first responder, so a field
// that could not be focused cannot receive the string either.
if (name === "type" && (focused.isError || focusBody.ok === false)) {
return { content: [{ type: "text", text: JSON.stringify(fail(computer, focusBody.error?.code ?? "focus_failed", focusBody.error?.message ?? "element could not be focused", { tool: name, stage: "focus" })) }], isError: true };
}
args = { ...args };
}
// Out-of-process runners (the desktop app for the local computer, the
// remote agent for ssh computers) get the request over the wire.
const backendMethod = BACKEND_METHOD[name];
// Scripting is honored on the local computer only. Remote agents refuse
// it too (their handler gates computerId), so a remote channel can never
// be steered into a shell — fail here first to save the hop.
if (name === "app_script" && computer.transport !== "local") {
throw new ServerError("unsupported_on_transport", `app_script runs on the local computer only — the ${computer.transport} transport stays a computer-use channel, never a shell`);
}
let data;
const ex = computer.transport === "local" || computer.transport === "ssh" || computer.transport === "docker" ? await executorFor(computer, binding) : null;
if (ex?.kind === "app") binding.usedApp = true;
// Zoom needs the bound parent raster up front (server-side check too, not
// only the backend) so it can bind the child raster after success.
let zoomParent = null;
if (name === "zoom") {
zoomParent = lastRasters.get(computer.id);
if (!zoomParent) throw new ServerError("no_raster", "no screenshot bound on this computer yet — call screenshot first so zoom has a source raster");
if (!Array.isArray(args.region) || args.region.length !== 4) throw new ServerError("bad_args", "zoom needs region [x, y, w, h] in last-raster pixels");
}
const sink = { reacquired: false };
if (typeof ex?.remote === "function" && REMOTE_TOOLS.has(backendMethod)) {
// ssh rides the persistent agent channel when the remote supports
// --serve; a channel that never produced a reply means an old agent,
// so fall back to one-shot for that binding rather than failing.View on GitHub (pinned to 73e0f67d83)