Hmbown/CodeWhale · error · ExecError

open_application needs a valid Harmony ability identifier

Error message

open_application needs a valid Harmony ability identifier

What it means

When open_application is given an ability name, it must match the same strict Harmony identifier pattern as the bundle; an invalid ability string is rejected with this ExecError before any `aa start` attempt. Valid ability names are single dot-free identifier segments like EntryAbility or MainAbility.

Solutions

  1. Pass the ability class name exactly (e.g. 'EntryAbility' or 'MainAbility'); omit ability to let the backend try common defaults.
  2. Pre-validate with /^[A-Za-z_][A-Za-z0-9_]*$/ and length ≤ 256.
  3. Check the app's module.json5 (srcEntry/abilities name) for the correct ability identifier.

Example fix

// before
await backend.open_application({ bundle_id: 'com.example.app', ability: 'entry-ability' });
// after
await backend.open_application({ bundle_id: 'com.example.app', ability: 'EntryAbility' });
Defensive patterns

Strategy: validation

Validate before calling

const ABILITY_ID = /^[A-Za-z_][A-Za-z0-9_]*$/;
if (ability != null && (typeof ability !== 'string' || ability.length > 256 || !ABILITY_ID.test(ability))) throw new TypeError('invalid Harmony ability identifier');

Type guard

const isHarmonyAbility = (v) => v == null || (typeof v === 'string' && v.length <= 256 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(v));

Try / catch

try { await backend.open_application({ bundle_id, ability }); } catch (e) { if (String(e.message).includes('ability identifier')) { await backend.open_application({ bundle_id }); /* omit ability, use defaults */ } else throw e; }

Prevention

When it happens

Trigger: Calling open_application({bundle_id:'com.example.app', ability:'entry ability'}) with spaces, hyphens, empty string, an ability longer than 256 chars, or a value starting with a digit.

Common situations: Copy-pasting ability names with typos or from UIAbility labels rather than class names; including a full element path or slice name instead of the ability class name; passing null-like values that are not actually undefined.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/harmonyos.mjs:186

      if (index !== 1) throw new ExecError("harmony backend exposes display 1 only");
      return { activeDisplay: 1 };
    },
    list_apps: async () => {
      const out = await deviceOut(["bm", "dump", "-a"], { timeoutMs: 25_000 });
      const bundles = out.split("\n").map((s) => s.trim()).filter((s) => /^[a-zA-Z][\w.]*$/.test(s));
      return { apps: bundles.map((b) => ({ name: b, bundle_id: b, kind: "bundle" })) };
    },
    list_windows: async (args = {}) => {
      rejectAppSelectors(args);
      const out = await deviceOut(["hidumper", "-s", "WindowManagerService", "-a", "-a"], { timeoutMs: 25_000 }).catch(() => "");
      const windows = out.split("\n").filter((l) => /Window Name|bundleName/i.test(l)).slice(0, 40).map((l) => ({ title: l.trim().slice(0, 160) }));
      return { windows: windows.length ? windows : [{ title: "(window list unavailable on this HarmonyOS build)" }] };
    },
    open_application: async ({ bundle_id: bid, ability, name } = {}) => {
      const bundle = bid ?? name;
      const identifier = (value) => typeof value === "string" && value.length <= 256 && /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(value);
      if (!identifier(bundle)) throw new ExecError("open_application needs a valid Harmony bundle identifier");
      if (ability != null && !identifier(ability)) throw new ExecError("open_application needs a valid Harmony ability identifier");
      const candidates = ability != null ? [ability] : ["EntryAbility", "MainAbility"];
      let last = null;
      for (const a of candidates) {
        const r = await shell(["aa", "start", "-b", escDeviceText(bundle), "-a", escDeviceText(a)]);
        if (r.code === 0 && !/Error|error/.test(r.stdout + r.stderr)) {
          return { launched: true, bundle, ability: a };
        }
        last = (r.stderr || r.stdout).trim().slice(0, 200);
      }
      throw new ExecError(`aa start failed: ${last}`);
    },
    get_app_state: async (args = {}) => {
      rejectAppSelectors(args);
      const tree = await dumpLayout();
      const els = flatten(tree);
      return { bundle_id: tree.attributes?.bundleName ?? null, elements: els, truncated: els.length >= 600 };
    },
    screenshot: async (args = {}) => {

View on GitHub (pinned to 73e0f67d83)