| 1 | # Refusal codes and the move that fixes them |
| 2 | |
| 3 | Every refusal is structured: `ok:false` with an `error.code` you can branch on. |
| 4 | Never retry a refusal unchanged — re-observe, re-target, or change route. |
| 5 | |
| 6 | ## Target and state |
| 7 | |
| 8 | | code | meaning | move | |
| 9 | | --- | --- | --- | |
| 10 | | `element_stale` | the live tree no longer matches the observation (user or app changed it) | `get_app_state` again and re-target | |
| 11 | | `unknown_state` | no observation on this computer (or the app was rebound) | observe first; bare indices bind the latest observation of the bound app | |
| 12 | | `state_wrong_computer` | `state_id` came from a different computer | observe on the computer you are acting on | |
| 13 | | `unknown_element` | index outside the cached tree | observe again; use `query`/`role` filters | |
| 14 | | `element_no_geometry` | element has no frame | use a coordinate target from a fresh raster | |
| 15 | | `degenerate_frame` | zero-size placeholder row (virtualized list) | scroll the real row into view, re-observe | |
| 16 | | `target_outside_raster` | coordinate outside the bound screenshot | take a fresh screenshot/zoom and use its pixels | |
| 17 | | `no_raster` | coordinate target with no raster bound | `screenshot` first | |
| 18 | | `window_blocked_by_modal_sheet` | an accessibility press would cross a sheet | deal with the sheet first | |
| 19 | | `window_ambiguous` | two windows share the resolved window's frame (stacked or identical geometry) | `list_windows`, pick one, pass `window_id` (or re-target the press) | |
| 20 | | `window_target_not_found` | PID has no eligible window | `list_windows`; open or pick the right app | |
| 21 | |
| 22 | ## Route and policy |
| 23 | |
| 24 | | code | meaning | move | |
| 25 | | --- | --- | --- | |
| 26 | | `shared_pointer_required` | background mode refuses pointer gestures | use element targets; shared desktop needs the user's explicit authorization | |
| 27 | | `background_scroll_unavailable` | no scrollbar at that point | target an observed scroll area | |
| 28 | | `menu_item_not_found` | exact title not present (menus expose items only while open) | check the exact title; an ellipsis is part of it | |
| 29 | | `menu_item_disabled` | item present but the app refuses it right now (often a missing key window) | use the window's own control element instead | |
| 30 | | `app_not_found` | selector missed — `open_application` names/bundle ids that resolve nowhere and dead pids report it too | `list_apps` (or `all:true`) for exact names/pids | |
| 31 | | `ambiguous_application` | `kill_app` name matched several running apps | pass `pid` to choose one | |
| 32 | | `protected_application` | the target is the Computer Use helper or its host | name the intended app instead; these cannot be terminated through the plugin | |
| 33 | | `browser_not_running` | browser action before `browser {action:"start"}` (or the browser went away) | start it; a closed CDP connection clears the session state | |
| 34 | | `browser_not_installed` | no Chromium-family browser found | install one, or set `CODEWHALE_CU_BROWSER_APP` to the app path | |
| 35 | | `selector_not_found` | no element matches the CSS selector on the current page | re-check the selector against a fresh `browser {action:"screenshot"}` or `browser {action:"status"}` | |
| 36 | | `unsupported_runtime` | this Node has no global WebSocket (browser transport) | use Node 22+ for the daemon/server running the plugin | |
| 37 | | `not_granted` | the session's capability grant (`CODEWHALE_CU_GRANT`) does not include this tool | work inside the grant; the host narrowed it deliberately | |
| 38 | | `consent_required` | no user decision exists for this app on the local computer | ask the user, then record it: `consent {action:"allow"\|"deny", app:"…"}` | |
| 39 | | `app_denied` | the user denied this app — the deny covers every spelling of it | do not work around it; only they can `consent {action:"revoke"}` | |
| 40 | | `foreground_consent_required` | `activate:true` needs the separate shared-pointer decision | ask, then `consent {action:"allow"\|"deny", scope:"foreground"}` — or keep working background (`activate:false`) | |
| 41 | | `foreground_denied` | the user denied shared-desktop (foreground) control | work background-only; do not retry `activate:true` | |
| 42 | | `frame_refused` | the app refused both the position and the size write | the window is fullscreen, tiled or otherwise not movable by the app | |
| 43 | | `trajectory_not_found` | no trajectory file matches the id (or none exist) | `trajectory {action:"status"}` lists recent files | |
| 44 | | `replay_too_large` | the trajectory exceeds the 200-turn replay cap | split it, or replay a pruned copy | |
| 45 | | `app_upgrade_required` | the helper predates the feature or is not running | restart/update the Codewhale Computer Use app | |
| 46 | | `unsupported_on_backend` | tool not implemented on that platform backend | check the platform note in the main skill | |
| 47 | | `unsupported_on_transport` | `app_script` sent to an ssh/docker/hdc computer — scripting is local-only so a remote channel never becomes a shell | run it on `local`, or use the host's own remote access | |
| 48 | | `docker_unavailable` | `computer spawn` found no reachable docker daemon | start Docker (or Colima); spawn needs the daemon, not just the CLI | |
| 49 | | `spawn_image_missing` | the requested spawn image is not present locally | build/pull it, or omit `image` to use the plugin's own Linux desktop image (auto-built on first spawn) | |
| 50 | | `spawn_failed` | provisioning failed or the desktop did not become ready | read the message; the failed container is removed automatically — fix the cause and spawn again | |
| 51 | | `invalid_container` | a docker registry entry lacks a valid container name | register it through `computer spawn`, never by hand | |
| 52 | | `cleanup_failed` | `docker rm` failed while tearing down a spawned computer | the registry entry is still removed; check `docker ps` for the labeled container and remove it manually | |
| 53 | | `script_error` | osascript exited non-zero; stderr is in the message | read the error, check the app's scripting dictionary (`sdef`), fix the script | |
| 54 | | `script_timeout` | the script — or a consent dialog — was still open at the deadline | narrow the script; a consent prompt is the person's choice, report it | |
| 55 | | `script_cancelled` | the script's own dialog was cancelled (-128) | the user declined in-app; stop or ask | |
| 56 | | `automation_denied` | -1743: the responsible app lacks Automation consent for the target | name System Settings → Privacy & Security → Automation; never retry it away | |
| 57 | | `permission` / `permissions_denied` | a grant is missing | name the permission and the Settings pane, then stop | |
| 58 | | `control_stopped` | the kill switch ended this session | report to the user; the session cannot resume | |
| 59 | | `cancelled` | the host cancelled the request | the input may or may not have landed — observe before retrying | |
| 60 | | `timeout` | the request exceeded its deadline | observe; only retry after confirming the first attempt did not land | |
| 61 | |
| 62 | `background_focus_required` means this path would borrow keyboard focus and |
| 63 | was refused before delivery. Use accessibility, browser control or a separate |
| 64 | computer; a typing pause does not authorize foreground control. |
| 65 | |
| 66 | ## Reading a receipt |
| 67 | |
| 68 | - `action_sent` / `verified` mean dispatch (and, where available, read-back) — |
| 69 | not task success. Verify the effect with a fresh observation. |
| 70 | - `front_lease` / `front_restored` describe focus accounting for window-record |
| 71 | deliveries in explicitly authorized foreground mode. `front_restored:false` is a person-visible event: say it out loud. |
| 72 | - `input_may_have_been_sent` on an error means the press left before the |
| 73 | failure: observe the target before doing anything else. |
| 74 |