{"record":{"id":"7a6786abda94a379","repo":"Hmbown/CodeWhale","slug":"computer-observation-required","errorCode":"computer_observation_required","errorMessage":"Computer route changed — call screenshot or get_app_state on the registered target before acting","messagePattern":"Computer route changed — call screenshot or get_app_state on the registered target before acting","errorType":"error_code","errorClass":"ServerError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/mcp/server.mjs","lineNumber":843,"sourceCode":"      const resolved = await resolveAppIdentity(computer, ref);\n      const allKeys = resolved ? [...new Set([...keys, ...consent.appKeys(resolved)])] : keys;\n      if (verb) {\n        const r = consent.record(computer.id, allKeys, verb, { remember: args.remember === true, name: resolved?.name ?? ref.name ?? null });\n        return { content: [{ type: \"text\", text: JSON.stringify(receipt(computer, { ok: true, tool: name, switched, scope, decision: verb, app: resolved ?? ref, keys: allKeys, persisted: r.persisted, note: `${resolved?.name ?? ref.name ?? ref.bundle_id ?? `pid ${ref.pid}`} ${verb === \"allow\" ? \"allowed\" : \"denied\"} ${r.persisted ? \"until revoked\" : \"for this session\"}.` })) }] };\n      }\n      const r = consent.revoke(computer.id, allKeys);\n      return { content: [{ type: \"text\", text: JSON.stringify(receipt(computer, { ok: true, tool: name, switched, scope, app: resolved ?? ref, keys: allKeys, ...r, note: \"Decisions removed — the next call targeting this app asks again.\" })) }] };\n    } catch (err) {\n      return { content: [{ type: \"text\", text: JSON.stringify(fail(computer, err.code ?? \"consent_error\", err.message ?? String(err), { tool: name, switched })) }], isError: true };\n    }\n  }\n\n  let binding;\n  let dispatched = false;\n  try {\n    binding = await bindComputer(computer);\n    if (binding.needsObservation && !ROUTE_INSPECTION_TOOLS.has(name)) {\n      throw new ServerError(\"computer_observation_required\", \"Computer route changed — call screenshot or get_app_state on the registered target before acting\");\n    }\n    // Per-app consent: the first call that targets an application on the local\n    // computer must carry a recorded user decision. open_application returns\n    // the grant so its resolved identity can be aliased below.\n    const gateResult = await consentCheck(computer, name, args);\n    if (name === \"run_actions\") {\n      const steps = args.steps;\n      if (!Array.isArray(steps) || steps.length < 1 || steps.length > 8) throw new ServerError(\"bad_args\", \"run_actions needs 1..8 steps\");\n      const results = [];\n      for (const [i, step] of steps.entries()) {\n        if (!step || typeof step.tool !== \"string\") throw new ServerError(\"bad_args\", `step ${i} needs a tool name`);\n        if (step.tool === \"run_actions\") throw new ServerError(\"bad_args\", \"run_actions cannot nest\");\n        if (!TOOL_NAMES.has(step.tool)) throw new ServerError(\"unknown_tool\", `unknown tool \"${step.tool}\"`);\n        const result = await callTool({ name: step.tool, arguments: { ...(step.arguments ?? {}), computer: computer.id } });\n        const body = JSON.parse(result.content[0].text);\n        results.push({ tool: step.tool, ok: body.ok !== false, receipt: body });\n        if (body.ok === false || result.isError) {\n          return { content: [{ type: \"text\", text: JSON.stringify(fail(computer, body.error?.code ?? \"step_failed\", body.error?.message ?? \"step failed\", { tool: \"run_actions\", switched, stopped_at: i, steps: results })) }], isError: true };","sourceCodeStart":825,"sourceCodeEnd":861,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/mcp/server.mjs#L825-L861","documentation":"Thrown after bindComputer() reports that the computer's route changed (needsObservation) when the requested tool is not in ROUTE_INSPECTION_TOOLS. After a route switch the server's cached view of the target is stale, so it demands a fresh observation — a screenshot or get_app_state on the newly registered target — before allowing action tools to run.","triggerScenarios":"Calling an action tool (e.g. run_actions, open_application) right after the computer route was switched/re-registered, without first calling screenshot or get_app_state on the new target.","commonSituations":"Automation scripts that assume one registration per session; a container restart or rebind changing the route mid-workflow; retry logic replaying actions against a re-registered computer.","solutions":["Call screenshot or get_app_state on the registered target once, then retry the original tool.","Re-read the current computer id from the registry and target calls at the new id.","If the route change was unintended, re-register/rebind the expected computer before resuming."],"exampleFix":"// before: acting on a stale route\nawait callTool({ name: \"run_actions\", arguments: { computer: \"box1\", steps: [...] } }); // throws computer_observation_required\n\n// after: observe first\nawait callTool({ name: \"screenshot\", arguments: { computer: \"box1\" } });\nawait callTool({ name: \"run_actions\", arguments: { computer: \"box1\", steps: [...] } });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await callTool({ name, arguments });\n} catch (e) {\n  if (e.code === \"computer_observation_required\") {\n    await callTool({ name: \"screenshot\", arguments: { computer: arguments.computer } });\n    return await callTool({ name, arguments }); // single observation-then-retry\n  }\n  throw e;\n}","preventionTips":["After any route change or re-registration, call screenshot/get_app_state before acting.","Avoid caching computer ids across route switches; read the current registration.","Interleave observation calls in long-running automation so the server's view stays fresh."],"tags":["stale-state","computer-use","mcp","observation-required"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}