{"record":{"id":"e2cf1e1ff84c32db","repo":"Hmbown/CodeWhale","slug":"open-application-needs-a-valid-harmony-bundle-identifier","errorCode":null,"errorMessage":"open_application needs a valid Harmony bundle identifier","messagePattern":"open_application needs a valid Harmony bundle identifier","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/harmonyos.mjs","lineNumber":185,"sourceCode":"    async switch_display({ index }) {\n      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    },","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/harmonyos.mjs#L167-L203","documentation":"open_application validates the bundle identifier against a strict Harmony pattern (dot-separated identifier segments, each starting with a letter/underscore, max 256 chars) before invoking `aa start`; anything else is rejected with this ExecError to prevent malformed arguments reaching the device shell.","triggerScenarios":"Calling open_application with a bundle_id/name that is undefined, empty, contains spaces/slashes/hyphens at segment starts, exceeds 256 chars, or is not a dot-separated identifier (e.g. 'My App', '/Applications/Safari.app', 'com.example-app').","commonSituations":"Passing macOS-style app names or .app paths to the Harmony backend; using display names instead of bundle ids; missing both bundle_id and name so bundle is undefined; hyphens from iOS-style reverse-DNS ids.","solutions":["Pass the exact Harmony bundle name as bundle_id (e.g. 'com.example.myapp'); list valid ids with `bm dump -a` via list_apps.","Ensure either bundle_id or name is provided and is a non-empty dot-separated identifier.","Pre-validate with the same regex: /^[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)*$/ and length ≤ 256.","Sanitize macOS/iOS identifiers (replace hyphens) or map app names to Harmony bundle ids before calling."],"exampleFix":"// before\nawait backend.open_application({ bundle_id: 'My App' });\n// after\nawait backend.open_application({ bundle_id: 'com.example.myapp' });","handlingStrategy":"validation","validationCode":"const HARmony_ID = /^[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)*$/;\nif (typeof bundle !== 'string' || bundle.length > 256 || !HARmony_ID.test(bundle)) throw new TypeError('invalid Harmony bundle identifier');","typeGuard":"const isHarmonyIdentifier = (v) => typeof v === 'string' && v.length <= 256 && /^[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(v);","tryCatchPattern":"try { await backend.open_application({ bundle_id: bid }); } catch (e) { if (String(e.message).includes('valid Harmony bundle identifier')) { const apps = await backend.list_apps(); /* resolve correct bundle id, retry */ } else throw e; }","preventionTips":["Source bundle ids from list_apps, never from display names or .app paths.","Validate identifiers with the same regex the backend uses before calling.","Require bundle_id explicitly instead of relying on the name fallback."],"tags":["harmonyos","validation","bundle-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"}