Hmbown/CodeWhale · error · ServerError
unknown_state
unknown_state
Error message
state_id "${target.state_id}" is unknown or expired — call get_app_state again What it means
resolveElement resolves the flat element addressing a caller saw in its last get_app_state observation. Every observation is stored under a state_id in a bounded in-memory map (appStates), so the server throws code "unknown_state" when the supplied state_id is no longer present — it was never valid or has been evicted/expired. The error is thrown before any action is taken so the caller re-observes rather than acting on a blind index.
Solutions
- Call get_app_state on the target computer again to obtain a fresh state_id, then immediately act using the new observation.
- Re-derive the target element index from the fresh observation (UI may have changed, so don't blindly reuse the old index).
- Verify the state_id string is passed through unmodified (no truncation, no interpolation into a template that mangles it).
- If states expire quickly in your workflow, interleave get_app_state between actions instead of caching observations across many steps.
Example fix
// before
await act({ type: "element", state_id: oldStateId, index: 3 });
// after
const st = await getAppState(computerId);
await act({ type: "element", state_id: st.state_id, index: 3 }); Defensive patterns
Strategy: retry
Validate before calling
if (!st || !stateIds.has(stateId)) { st = await getAppState(computerId); stateId = st.state_id; } Type guard
const hasState = (stateId) => typeof stateId === "string" && stateId.length > 0 && knownStateIds.has(stateId);
Try / catch
try {
return await act({ type: "element", state_id, index });
} catch (e) {
if (e.code === "unknown_state") {
const fresh = await getAppState(computerId);
return await act({ type: "element", state_id: fresh.state_id, index: relocate(fresh, index) });
}
throw e;
} Prevention
- Re-observe (get_app_state) immediately before each action instead of caching state_ids across turns.
- Keep state_id and its element indexes together as one immutable observation record.
- Re-observe after any server restart or reconnect.
- Evict your own cached observations when the server reports capacity/age limits.
When it happens
Trigger: Any act/click/type-style tool call with target.type === "element" where target.state_id is set but appStates.get(state_id) returns null: the id was mistyped, the server restarted, the state cache evicted it after capacity/age limits, or the session was reconnected.
Common situations: A long-running agent loop holds state_ids from many turns ago while the server only keeps recent observations; scripts written against one server process replayed against a restarted one; a caller copies a state_id between two computers/sessions; logging/truncation mangles the id.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/c7c869c9769f3a75.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:161
throw err;
}
}
async function getBackend(computer, binding) {
if (!binding.backend) binding.backend = (await backendFor(computer)).backend;
return binding.backend;
}
/**
* Element target -> enriched target with cached app identity and AX path.
* An explicit state_id pins a specific observation; a bare index addresses
* the latest observation on this computer — the flat addressing a caller
* uses when it acts on what it just saw.
*/
function resolveElement(target, computer) {
const stateId = target.state_id ?? latestStateByComputer.get(computer.id);
const st = stateId ? appStates.get(stateId) : null;
if (!st) throw new ServerError("unknown_state", target.state_id
? `state_id "${target.state_id}" is unknown or expired — call get_app_state again`
: "no observation on this computer yet — call get_app_state first");
const el = st.elements[target.index];
if (!el) throw new ServerError("unknown_element", `element index ${target.index} is outside state ${stateId} (0..${st.elements.length - 1})`);
return { state: st, element: el, stateId };
}
class ServerError extends Error {
constructor(code, message, extra = null) { super(message); this.code = code; if (extra) this.extra = extra; }
}
/** Map raster-pixel coordinates to screen points using the bound raster. */
function rasterToPoints(computerId, x, y) {
const r = lastRasters.get(computerId);
if (!r) throw new ServerError("no_raster", "no screenshot bound on this computer yet — call screenshot first so pixel targets have a frame");
if (r.pixels?.w != null && r.pixels?.h != null && (x < 0 || y < 0 || x >= r.pixels.w || y >= r.pixels.h)) {
throw new ServerError("target_outside_raster", `target (${x},${y}) is outside the bound raster (${r.pixels.w}x${r.pixels.h} pixels) — take a fresh screenshot`);
}View on GitHub (pinned to 73e0f67d83)