Hmbown/CodeWhale · error · ExecError

application not found or name is ambiguous in the AT-SPI…

Error message

application not found or name is ambiguous in the AT-SPI tree — use a unique exact app_ref.name

What it means

The pyatspi walk completed and returned JSON, but reported found=false: no application matched app_ref.name exactly (or the name matched more than one application ambiguously) in the AT-SPI tree. The library refuses to guess which application was meant.

Solutions

  1. Use the exact AT-SPI application name — list the tree first or match WM_CLASS exactly.
  2. Enable in-app accessibility for the target (e.g. Chromium/Electron: --force-renderer-accessibility or GTK_MODULES=gail).
  3. Pick a unique name if several apps share one; disambiguate via a different app or window.
  4. Confirm the app is actually running and registered: dump the AT-SPI top-level application names with pyatspi before calling.

Example fix

// before
await backend.get_app_state({ app_ref: { name: 'chrome' } }); // ambiguous/not found
// after
await backend.get_app_state({ app_ref: { name: 'Google Chrome' } }); // exact AT-SPI app name
Defensive patterns

Strategy: validation

Validate before calling

const appNames = await listAtspiAppNames(); // dump tree top-level names first
if (!appNames.includes(app_ref.name)) throw new Error(`app '${app_ref.name}' not in AT-SPI tree; known: ${appNames}`);

Try / catch

try {
  return await backend.get_app_state({ app_ref });
} catch (e) {
  if (String(e.message).includes('not found or name is ambiguous')) {
    throw new Error(`Use exact AT-SPI app name, not '${app_ref.name}'`);
  }
  throw e;
}

Prevention

When it happens

Trigger: app_ref.name does not equal the accessibility application name (e.g. 'chrome' vs 'Google-chrome'); passing a window title or process name instead of the AT-SPI application name; the target app exposes no accessibility (Electron apps without accessibility enabled, Java apps without the java-atk wrapper); multiple windows/apps share the same name.

Common situations: Electron/Chromium apps that only register in the a11y tree after accessibility is force-enabled (GNOME_ACCESSIBILITY, or Chromium's --force-renderer-accessibility); fuzzy-matching a display title; apps whose WM_CLASS differs from their AT-SPI app name.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

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

        } catch { /* no session/tools — the launch itself is still fine */ }
      }
      spawnDetached(target, urlArg ? [urlArg] : [], "", true);
      await new Promise((r) => setTimeout(r, 500));
      let focusRestored = false;
      if (prevWindow) {
        try {
          focusRestored = (await run("xdotool", ["windowactivate", prevWindow], { timeoutMs: 3_000 })).code === 0;
        } catch { /* best-effort */ }
      }
      return { launched: true, name: target, url: urlArg ?? null, activate: activate === true, ...(activate === true ? {} : { focus_restored: focusRestored }) };
    },
    get_app_state: async ({ app_ref, window_id } = {}) => {
      const name = appName(app_ref);
      if (window_id !== undefined) throw Object.assign(new ExecError("Linux app-state window_id selection is unavailable"), { code: "unsupported_selector" });
      const t = await run("python3", ["-c", PYATSPI_WALK, name, "10", "500"], { timeoutMs: 45_000 }).then((r) =>
        tryJson((r.stdout.trim().split("\n").pop() ?? ""), null));
      if (!t) throw new ExecError("AT-SPI walk failed — is python3-pyatspi installed and the desktop running an accessibility bus (AT_SPI_BUS)?");
      if (!t.found) throw new ExecError("application not found or name is ambiguous in the AT-SPI tree — use a unique exact app_ref.name");
      return t;
    },
    screenshot: async (args = {}) => {
      rejectWindowSelectors(args);
      const { display, region, path: outPath } = args;
      if (outPath != null) outputPath(outPath);
      await probeSession();
      const dir = recordingsDir();
      fs.mkdirSync(dir, { recursive: true });
      const file = outPath || path.join(dir, `shot-${new Date().toISOString().replace(/[:.]/g, "-")}-${crypto.randomBytes(3).toString("hex")}.png`);
      await takeShot(file, region);
      const dims = pngSize(file);
      lastRaster = {
        file,
        bytes: fs.statSync(file).size,
        // Region rasters describe the region; full shots get geometry from the
        // PNG itself (Linux shots are always scale 1: points == pixels).
        points: region ? { x: region[0], y: region[1], w: region[2], h: region[3] } : dims ? { x: 0, y: 0, w: dims.w, h: dims.h } : null,

View on GitHub (pinned to 73e0f67d83)