Hmbown/CodeWhale · error · Error

The background check requires the installed macOS app.

Error message

The background check requires the installed macOS app.

What it means

runBackgroundCheck in the computer-use plugin's background-check.mjs refuses to run unless the host is macOS and a bundle path for the installed Computer Use app was supplied. The check drives a practice app inside the installed .app bundle, so without it there is nothing to launch. This is a precondition guard at the top of the function, raised before any process is spawned.

Solutions

  1. Install the Computer Use macOS app and pass its bundle path: runBackgroundCheck({ bundle: '/Applications/Computer Use.app' }).
  2. On non-macOS hosts, skip the background check or route it to a macOS machine; the check cannot run there.
  3. Resolve the bundle programmatically (e.g. via mdfind or a known install path) instead of hardcoding, so updates to the install location do not break the call.

Example fix

// before
await runBackgroundCheck(); // fails: no bundle, maybe not macOS

// after
if (process.platform !== "darwin") throw new Error("Background check needs macOS.");
await runBackgroundCheck({ bundle: "/Applications/Computer Use.app" });
Defensive patterns

Strategy: validation

Validate before calling

if (process.platform !== "darwin") throw new Error("Background check needs macOS.");
if (!bundle) throw new Error("Pass the installed Computer Use app bundle path.");

Type guard

function canRunBackgroundCheck(opts) {
  return process.platform === "darwin" && typeof opts?.bundle === "string" && opts.bundle.length > 0;
}

Try / catch

try {
  await runBackgroundCheck({ bundle });
} catch (e) {
  if (String(e.message).includes("requires the installed macOS app")) {
    throw new Error("Run this on macOS with the Computer Use app installed and pass { bundle }.");
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling runBackgroundCheck() when process.platform !== 'darwin', or calling it on macOS without passing { bundle } (or passing bundle: null/undefined).

Common situations: Running the check on Linux/CI (non-macOS), invoking it from a dev environment where the app was never installed, or a caller that forgot to resolve and pass the installed app's bundle path.

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/73e3889bd47646b7. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/app/background-check.mjs:11

import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import crypto from "node:crypto";
import { spawn } from "node:child_process";
import { setTimeout as delay } from "node:timers/promises";
import { handle, closeSession } from "../src/app-handler.mjs";

/** Uses the same daemon backend and cancellation path as connected hosts. */
export async function runBackgroundCheck({ bundle, demoDirectory } = {}) {
  if (process.platform !== "darwin" || !bundle) throw new Error("The background check requires the installed macOS app.");
  const executable = path.join(bundle, "Contents", "Resources", "Practice.app", "Contents", "MacOS", "practice");
  if (!fs.existsSync(executable)) throw new Error("Update the Computer Use app to run the background check.");
  const sessionId = `setup-${crypto.randomUUID()}`;
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 15_000);
  const child = spawn(executable, [], { stdio: ["ignore", "pipe", "pipe"] });
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), "cu-setup-capture-"));
  let ready = false, applied = null, latest = null, buffer = "", spawnError = null;
  child.on("error", error => { spawnError = error; });
  child.stderr.on("data", () => {});
  child.stdout.setEncoding("utf8");
  child.stdout.on("data", chunk => {
    buffer += chunk;
    if (buffer.length > 16_384) { controller.abort(); return; }
    let newline;
    while ((newline = buffer.indexOf("\n")) >= 0) {
      const line = buffer.slice(0, newline); buffer = buffer.slice(newline + 1);
      try { const result = JSON.parse(line); latest = result; if (result.event === "ready") ready = true; if (result.event === "applied") applied = result; } catch { /* Cocoa diagnostics are not receipts. */ }

View on GitHub (pinned to 73e0f67d83)