Hmbown/CodeWhale · error · ExecError

native accessibility helper needs a built app or Xcode…

Error message

native accessibility helper needs a built app or Xcode Command Line Tools: ${r.stderr}

What it means

The macOS computer-use backend compiles a native Objective-C accessibility helper on demand, caching it under ~/.codewhale-cu/bin. When no pre-built app bundle provides the helper and invoking `clang` to build it fails, this error is thrown with clang's stderr. It means the machine cannot compile the helper because the Xcode Command Line Tools are missing or broken.

Solutions

  1. Install or repair the toolchain: run `xcode-select --install` and accept the license with `sudo xcode-select --license`.
  2. Verify clang works: `xcode-select -p` and `clang --version` must succeed.
  3. If Xcode is installed, ensure `xcode-select -s /Applications/Xcode.app/Contents/Developer` points at it and Xcode has finished its first-run component install.
  4. Clear a corrupt cached/partial helper: remove ~/.codewhale-cu/bin/accessibility-* and retry.
  5. As a fallback, ship or point the backend at a pre-built helper app so clang compilation is never needed.

Example fix

// before (shell)
native("click", ...)
// error: native accessibility helper needs a built app or Xcode Command Line Tools: clang: error: no developer tools found

// after (shell, one-time setup)
xcode-select --install && clang --version
Defensive patterns

Strategy: fallback

Validate before calling

const { execSync } = require('child_process');
function clangAvailable() {
  try { execSync('clang --version', { stdio: 'pipe' }); return true; } catch { return false; }
}

Try / catch

try {
  await startComputerUse();
} catch (e) {
  if (String(e.message).includes('needs a built app or Xcode Command Line Tools')) {
    console.error('Install toolchain: xcode-select --install');
  }
  throw e;
}

Prevention

When it happens

Trigger: nativeHelper() is called (first use of any native input/capture tool), no cached helper exists at ~/.codewhale-cu/bin/accessibility-<hash>, and the `clang -fobjc-arc ... -o <tmp>` compile exits non-zero (60s timeout or compile/link failure).

Common situations: Fresh macOS installs without Xcode Command Line Tools; running after an Xcode upgrade that invalidates the toolchain; `xcode-select` pointing at a bare Xcode.app requiring license acceptance; PATH lacking clang (no developer directory selected); a corrupted partial build left in the cache dir from a timed-out build.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


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

Appendix: source

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

      if (fs.existsSync(packaged)) helper = packaged;
    }
    // A source checkout (plugin installs in other hosts) self-compiles an
    // unsigned helper, which has no TCC grant. Prefer the installed app's
    // signed helper so accessibility and screen-recording grants carry over.
    if (!helper || !fs.existsSync(helper)) {
      const installed = path.join(os.homedir(), "Applications", "Codewhale Computer Use.app", "Contents", "MacOS", "accessibility");
      if (fs.existsSync(installed)) helper = installed;
    }
    if (!helper || !fs.existsSync(helper)) {
      const source = fileURLToPath(new URL("./darwin-accessibility.m", import.meta.url));
      const hash = crypto.createHash("sha256").update(fs.readFileSync(source)).update(fs.readFileSync(new URL("./darwin-recording.h", import.meta.url))).update(fs.readFileSync(new URL("./darwin-ocr.h", import.meta.url))).digest("hex").slice(0, 16);
      const dir = path.join(os.homedir(), ".codewhale-cu", "bin");
      fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
      helper = path.join(dir, `accessibility-${hash}`);
      if (!fs.existsSync(helper)) {
        const tmp = `${helper}-${process.pid}`;
        const r = await runL("clang", ["-fobjc-arc", "-Os", "-framework", "Cocoa", "-framework", "ApplicationServices", "-framework", "ScreenCaptureKit", "-framework", "AVFoundation", "-framework", "CoreMedia", "-framework", "Vision", source, "-o", tmp], { timeoutMs: 60_000 });
        if (r.code !== 0) throw new ExecError(`native accessibility helper needs a built app or Xcode Command Line Tools: ${r.stderr}`, r);
        fs.renameSync(tmp, helper);
      }
    }
    return helper;
  }

  function requireFocusControl() {
    if (!state.foregroundInput) throw Object.assign(new ExecError("This action would take keyboard focus and was not sent in background mode. Use an accessibility action, browser control, or a separate computer."), { code: "background_focus_required" });
  }

  async function native(tool, args = {}) {
    // Window-addressed events still borrow keyboard focus. Block before even
    // starting an older installed helper, including the app-scoped fallback.
    if (["bg_pointer", "bg_key"].includes(tool) || (tool === "pointer_sequence" && args.app_scoped)) requireFocusControl();
    // Every resolved target (element center or screen point) is where the
    // action lands; tracking it here means the preview cursor follows element
    // actions, not just raw pointer events.
    if (tool === "type" && !state.foregroundInput && (await native("input_capabilities"))?.background_focus_guard !== 1) {

View on GitHub (pinned to 73e0f67d83)