Hmbown/CodeWhale · error · ExecError

open_application first to choose the preview app

Error message

open_application first to choose the preview app

What it means

Thrown when enabling live preview (preview({enabled:true})) in the macOS backend before any target application has been chosen. The preview loop renders through a dedicated preview app that open_application establishes (stored in state.inputApp); without it there is nothing to attach the preview surface to. The backend requires that ordering rather than silently picking an app.

Solutions

  1. Call open_application (with the target app name/pid) before enabling preview.
  2. If preview should be off, call preview({enabled:false}) — that path is allowed and just notifies the helper.
  3. Check for an earlier thrown error that prevented open_application from completing.

Example fix

// before
const session = createBackend();
await session.preview(); // no app chosen yet
// after
const session = createBackend();
await session.open_application({ name: 'Finder' });
await session.preview();
Defensive patterns

Strategy: validation

Validate before calling

if (!session.inputApp) {
  await session.open_application({ name: targetAppName });
}
await session.preview();

Type guard

null

Try / catch

try {
  await session.preview();
} catch (e) {
  if (String(e.message).includes('open_application first')) {
    await session.open_application({ name: 'Finder' });
    await session.preview();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling preview() or preview({enabled:true}) as the first action in a session, or after the preview app was never opened, instead of calling open_application first.

Common situations: Scripts that enable preview at startup before selecting a target app; sessions where an earlier error aborted open_application so state.inputApp was never set.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:995

          } else {
            t.ocr = { status: "unavailable", engine: "apple_vision", reason: ocr?.reason ?? "The native OCR helper needs an update or returned mismatched image dimensions", blocks: [], raster };
          }
        } catch (error) {
          throwIfAborted();
          if (error.code === "cancelled") throw error;
          t.ocr = { status: "unavailable", engine: "apple_vision", reason: error.message, blocks: [], ...(raster ? { raster } : {}) };
        }
      }
      return t;
    },
    resolve_element: async ({ app_ref, windowIndex, path: pathArr } = {}) => {
      const r = await native("resolve_element", { app_ref, windowIndex: windowIndex ?? 0, path: pathArr ?? [] });
      return { found: !!r?.found, element: r?.element ?? null, reason: r?.reason ?? null };
    },
    preview: async ({ enabled = true } = {}) => {
      state.previewEnabled = enabled;
      if (!enabled) { stopPreviewLoop(); await quiescePreview(); await native("preview_notify", { enabled: false }); return { enabled: false }; }
      if (!state.inputApp) throw new ExecError("open_application first to choose the preview app");
      return updatePreview(true);
    },
    screenshot,
    zoom,
    left_click: async ({ target, strategy = "auto" } = {}) => {
      if (target?.type !== "element" || strategy === "event" || strategy === "app") return pointerClick("left", target?.x, target?.y, 1, strategy);
      if (!["auto", "a11y"].includes(strategy)) throw new ExecError(`strategy must be auto, a11y, app or event (got ${JSON.stringify(strategy)})`);
      try {
        assertBoundElement(target);
        if ((await native("input_capabilities"))?.element_identity !== 1) throw new ExecError("native helper needs an update for element identity validation");
        const semantic = ["AXTextField", "AXTextArea", "AXComboBox", "AXRow", "AXCell", "AXMenuItem"].includes(target.role);
        if (semantic) await requireBackgroundActions();
        const receipt = await native(semantic ? "click_element" : "perform_action", { target, action: "AXPress" });
        if (!receipt?.action_sent) throw new ExecError("element press was not acknowledged");
        return { ...receipt, action: receipt.action ?? "AXPress", strategy: "a11y", pointer_moved: false,
          element: { role: target.role, label: target.label ?? null }, verified: receipt.verified ?? false, verification_required: "observation" };
      } catch (error) {
        // An AX frame can cover other controls. Never turn a refused or

View on GitHub (pinned to 73e0f67d83)