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
- Pass the ability class name exactly (e.g. 'EntryAbility' or 'MainAbility'); omit ability to let the backend try common defaults.
- Pre-validate with /^[A-Za-z_][A-Za-z0-9_]*$/ and length ≤ 256.
- 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
- Omit ability unless you know the exact UIAbility class name.
- Use PascalCase class names like EntryAbility, not slugs or labels.
- Look the ability up in the app's module.json5.
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
- invalid_target
- open_application needs a valid Harmony bundle identifier
- unsupported key " " on harmony backend
- 1
- A pinned task provider requires an explicit model
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)