Hmbown/CodeWhale · error · ServerError
state_wrong_computer
state_wrong_computer
Error message
state_id "${stateId}" belongs to computer "${state.computerId}", not "${computer.id}" — call get_app_state on that computer again What it means
After resolveElement succeeds for an element target, normalizeTarget checks the resolved state's owning computer: if state.computerId differs from the computer the action is addressed to, it throws code "state_wrong_computer". Element indexes are only meaningful within the observation that produced them, so using a state captured on machine A to act on machine B is refused outright.
Solutions
- Address the action to the computer the state came from, or call get_app_state on the intended computer and use its state_id.
- Track (computerId, state_id) pairs together in your agent loop; never split them.
- Check for computer-id typos or stale variables in multi-machine scripts.
- If you genuinely want the same element on another machine, observe that machine and match by role/label, not by index.
Example fix
// before
await click({ computerId: "vm-b", target: { type: "element", state_id: stateFromVmA, index: 2 } });
// after
const stB = await getAppState("vm-b");
await click({ computerId: "vm-b", target: { type: "element", state_id: stB.state_id, index: 2 } }); Defensive patterns
Strategy: type-guard
Validate before calling
const obs = observations.get(stateId);
if (!obs || obs.computerId !== computerId) throw new Error(`state ${stateId} does not belong to computer ${computerId}`); Type guard
const stateBelongsTo = (stateId, computerId) => observations.get(stateId)?.computerId === computerId;
Try / catch
try {
return await act({ computerId, target: { type: "element", state_id, index } });
} catch (e) {
if (e.code === "state_wrong_computer") {
const owner = e.extra?.computerId ?? ownerOf(stateId);
return await act({ computerId: owner, target: { type: "element", state_id, index } });
}
throw e;
} Prevention
- Store (computerId, state_id) as a pair and always pass both from the same record.
- In multi-machine loops, resolve observations per computer; never share state_ids across machines.
- Assert the computer id at the call site, not deep in a helper.
- To act on a different machine, observe it and match elements by role/label.
When it happens
Trigger: target.type === "element" where target.state_id (or the computer's latest state) resolves to a state whose state.computerId !== computer.id — e.g. an agent managing multiple VMs passes the wrong computerId alongside a state_id from another machine.
Common situations: Multi-computer automation loops with mismatched bookkeeping between the observation step and the action step; copy-pasted tool calls where only the computer id was edited; parallel agents each holding states for several machines and crossing wires.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- unknown_state
- A managed, project or plugin connector already uses this…
- Absolute path should not warn
- active plugin registry is missing its pre-dotenv…
- agent should stay registered
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/4314d5a5b6c6d42f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:206
* moved-but-identical elements are re-aimed at their fresh center
* (sink.reacquired = true so the receipt can say target_reacquired).
*/
async function normalizeTarget(computer, target, kind, resolve, sink) {
if (target?.type === "coordinate") {
if (target.space === "screen") {
if (!Number.isFinite(target.x) || !Number.isFinite(target.y)) {
throw new ServerError("bad_target", "screen coordinates must be finite numbers");
}
return { x: Math.round(target.x), y: Math.round(target.y), strategy: "event", coordinate_space: "screen" };
}
if (target.x < 0 || target.y < 0) throw new ServerError("bad_target", "raster coordinates must be non-negative");
const pt = rasterToPoints(computer.id, target.x, target.y);
return { x: Math.round(pt.x), y: Math.round(pt.y), strategy: "event", coordinate_space: "raster" };
}
if (target?.type === "element") {
const { state, element, stateId } = resolveElement(target, computer);
if (state.computerId && state.computerId !== computer.id) {
throw new ServerError("state_wrong_computer", `state_id "${stateId}" belongs to computer "${state.computerId}", not "${computer.id}" — call get_app_state on that computer again`);
}
// The receipt must name the observation actually resolved — a bare index
// binds the computer's latest state, so reporting `target.state_id` would
// say "undefined" for the common case.
const where = `state ${stateId} (${state.app_ref?.name ?? state.app_ref?.bundle_id ?? `pid ${state.app_ref?.pid}`})`;
let fresh = null;
if (resolve) {
const res = await resolve({ app_ref: state.app_ref, windowIndex: element.windowIndex ?? 0, path: element.path });
if (!res?.found || !res.element) {
throw new ServerError("element_stale", `element ${target.index} of ${where} no longer resolves (${res?.reason ?? "not_found"}) — the user or the app may have changed it; call get_app_state again`);
}
fresh = res.element;
if (fresh.role !== element.role) {
throw new ServerError("element_stale", `element ${target.index} of ${where} changed role (${element.role} → ${fresh.role}) — call get_app_state again`);
}
// In-place replacement: same role and geometry but a different label is
// still a different element (e.g. "Load" → "Confirm").
if (fresh.label !== element.label) {View on GitHub (pinned to 73e0f67d83)