Hmbown/CodeWhale · error · ExecError

aa start failed

Error message

aa start failed: ${last}

What it means

After trying each candidate ability with `aa start`, open_application throws this ExecError containing the last device-side error output (up to 200 chars) when no candidate launches successfully. It means the bundle/ability exists syntactically but the device refused to start it.

Solutions

  1. Read the embedded device output for the real cause ('Ability does not exist', 'install failed', etc.).
  2. Verify the bundle is installed via list_apps (`bm dump -a`) and use an exact bundle id.
  3. Find the app's real ability name (module.json5) and pass it explicitly as ability.
  4. Retry after confirming the device screen is unlocked; reboot the device or restart hdc if the error persists.

Example fix

try {
  await backend.open_application({ bundle_id: 'com.example.app' });
} catch (e) {
  if (String(e.message).startsWith('aa start failed')) {
    const apps = await backend.list_apps();
    const real = apps.apps.find(a => a.bundle_id.includes('example'));
    if (real) await backend.open_application({ bundle_id: real.bundle_id });
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const apps = await backend.list_apps();
if (!apps.apps.some(a => a.bundle_id === bid)) console.warn(`bundle ${bid} not installed on device`);

Type guard

null

Try / catch

try { await backend.open_application({ bundle_id: bid }); } catch (e) { if (String(e.message).startsWith('aa start failed')) { console.error('device said:', e.message); /* verify install + ability name, then retry */ } else throw e; }

Prevention

When it happens

Trigger: `aa start -b <bundle> -a <ability>` exits non-zero or prints Error for every candidate ability (EntryAbility, MainAbility or the explicit one) — e.g. the app is not installed, the ability name does not exist for that bundle, or the device refuses the launch.

Common situations: Typo'd bundle id for an app that is not installed on the device; app's actual ability class differs from EntryAbility/MainAbility; app disabled or device policy blocking `aa start`; stale device state requiring reboot.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

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

      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 = {}) => {
      rejectAppSelectors(args);
      const { path: outPath } = args;
      const dir = process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), ".codewhale-cu", "recordings");
      fs.mkdirSync(dir, { recursive: true });
      const file = outPath || path.join(dir, `shot-${new Date().toISOString().replace(/[:.]/g, "-")}-${crypto.randomBytes(3).toString("hex")}.jpeg`);
      await snapshot(file);
      const buf = fs.readFileSync(file);
      displayPixels = jpegSize(buf) ?? displayPixels;
      return { file, bytes: buf.length, pixels: jpegSize(buf), scale: 1, points: jpegSize(buf) };
    },

View on GitHub (pinned to 73e0f67d83)