Hmbown/CodeWhale · error · RuntimeError

element_read_only

Error message

element_read_only

What it means

set_value on the linux backend writes text via the AT-SPI EditableText interface when the target exposes it. If the element's state lacks STATE_EDITABLE — meaning the interface exists but the control rejects text modification — the script raises RuntimeError("element_read_only") instead of attempting a doomed write.

Solutions

  1. Target the actual editable input rather than the read-only display element
  2. Remove the read-only condition in the application under automation first
  3. Use clipboard/keyboard strategies (if exposed by the backend) instead of direct text-set
  4. Verify the target selector resolves to the intended widget, not a sibling label

Example fix

// before
await backend.set_value({ target: "#summary-view", value: "hi" }) // read-only
// after
await backend.set_value({ target: "#summary-input", value: "hi" });
Defensive patterns

Strategy: validation

Validate before calling

const state = await backend.get_state?.(target);
if (state && !state.editable) throw new Error(`target ${target} is read-only`);

Type guard

const isEditable = (s) => s?.editable === true;

Try / catch

try { await backend.set_value({ target, value }); } catch (e) { if (String(e).includes("element_read_only")) { return useKeyboardFallback(target, value); } throw e; }

Prevention

When it happens

Trigger: Calling set_value on a read-only text view, a label, a static document region, or any widget whose AT-SPI state omits STATE_EDITABLE while it still implements queryEditableText-adjacent interfaces.

Common situations: Automating a results/log pane that looks like a text field but is read-only; trying to edit a locked form after submission; targeting a password or computed field rendered as non-editable.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/38da1add92e4629a. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/linux.mjs:703

          await xdotool(["keydown", k]);
          await wait(d * 1000);
        } finally { await releaseKey(k); }
      } else await waylandKey(text, { holdMs: Math.round(d * 1000) });
      return { action_sent: true, key: k, heldSec: d };
    },
    set_value: async ({ target, value }) => {
      // Select a supported interface before sending input. A refused write or
      // failed readback must never trigger a second, ambiguously applied edit.
      const out = await atspiResolve(target, `    state = found.getState()
    if not state.contains(pyatspi.STATE_ENABLED):
        raise RuntimeError("element_disabled")
    try:
        editor = found.queryEditableText()
    except NotImplementedError:
        editor = None
    if editor is not None:
        if not state.contains(pyatspi.STATE_EDITABLE):
            raise RuntimeError("element_read_only")
        if not editor.setTextContents(extra):
            raise RuntimeError("value_rejected")
        text = found.queryText()
        after = text.getText(0, text.characterCount)
        if after != extra:
            raise RuntimeError("value_verification_failed")
    else:
        import math
        desired = float(extra)
        if not math.isfinite(desired):
            raise RuntimeError("invalid_value")
        numeric = found.queryValue()
        numeric.currentValue = desired
        after = numeric.currentValue
        if after != desired:
            raise RuntimeError("value_verification_failed")
    print(json.dumps({"ok": True, "after": after}))`, String(value));
      if (!out.ok) throw new ExecError(`set_value failed: ${out.code}`);

View on GitHub (pinned to 73e0f67d83)