{"record":{"id":"bf36ce1138d788d2","repo":"Hmbown/CodeWhale","slug":"open-application-needs-a-valid-harmony-ability-identifier","errorCode":null,"errorMessage":"open_application needs a valid Harmony ability identifier","messagePattern":"open_application needs a valid Harmony ability identifier","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/harmonyos.mjs","lineNumber":186,"sourceCode":"      if (index !== 1) throw new ExecError(\"harmony backend exposes display 1 only\");\n      return { activeDisplay: 1 };\n    },\n    list_apps: async () => {\n      const out = await deviceOut([\"bm\", \"dump\", \"-a\"], { timeoutMs: 25_000 });\n      const bundles = out.split(\"\\n\").map((s) => s.trim()).filter((s) => /^[a-zA-Z][\\w.]*$/.test(s));\n      return { apps: bundles.map((b) => ({ name: b, bundle_id: b, kind: \"bundle\" })) };\n    },\n    list_windows: async (args = {}) => {\n      rejectAppSelectors(args);\n      const out = await deviceOut([\"hidumper\", \"-s\", \"WindowManagerService\", \"-a\", \"-a\"], { timeoutMs: 25_000 }).catch(() => \"\");\n      const windows = out.split(\"\\n\").filter((l) => /Window Name|bundleName/i.test(l)).slice(0, 40).map((l) => ({ title: l.trim().slice(0, 160) }));\n      return { windows: windows.length ? windows : [{ title: \"(window list unavailable on this HarmonyOS build)\" }] };\n    },\n    open_application: async ({ bundle_id: bid, ability, name } = {}) => {\n      const bundle = bid ?? name;\n      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);\n      if (!identifier(bundle)) throw new ExecError(\"open_application needs a valid Harmony bundle identifier\");\n      if (ability != null && !identifier(ability)) throw new ExecError(\"open_application needs a valid Harmony ability identifier\");\n      const candidates = ability != null ? [ability] : [\"EntryAbility\", \"MainAbility\"];\n      let last = null;\n      for (const a of candidates) {\n        const r = await shell([\"aa\", \"start\", \"-b\", escDeviceText(bundle), \"-a\", escDeviceText(a)]);\n        if (r.code === 0 && !/Error|error/.test(r.stdout + r.stderr)) {\n          return { launched: true, bundle, ability: a };\n        }\n        last = (r.stderr || r.stdout).trim().slice(0, 200);\n      }\n      throw new ExecError(`aa start failed: ${last}`);\n    },\n    get_app_state: async (args = {}) => {\n      rejectAppSelectors(args);\n      const tree = await dumpLayout();\n      const els = flatten(tree);\n      return { bundle_id: tree.attributes?.bundleName ?? null, elements: els, truncated: els.length >= 600 };\n    },\n    screenshot: async (args = {}) => {","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/harmonyos.mjs#L168-L204","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait backend.open_application({ bundle_id: 'com.example.app', ability: 'entry-ability' });\n// after\nawait backend.open_application({ bundle_id: 'com.example.app', ability: 'EntryAbility' });","handlingStrategy":"validation","validationCode":"const ABILITY_ID = /^[A-Za-z_][A-Za-z0-9_]*$/;\nif (ability != null && (typeof ability !== 'string' || ability.length > 256 || !ABILITY_ID.test(ability))) throw new TypeError('invalid Harmony ability identifier');","typeGuard":"const isHarmonyAbility = (v) => v == null || (typeof v === 'string' && v.length <= 256 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(v));","tryCatchPattern":"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; }","preventionTips":["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."],"tags":["harmonyos","validation","ability-identifier"],"backgroundTag":"invalid-identifier-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}