---
name: hmos-fix-build-errors
description: Build a HarmonyOS project via CLI and automatically fix compile errors in a loop until the build succeeds. Signed vs unsigned is detected from the project's own build-profile.json5; pass --signed only to assert that a signed artifact is required, which turns "project has no signing config" into a hard stop.
argument-hint: <harmony_project_dir> [deveco-studio-path] [--signed]
allowed-tools: Agent, Read, Write, Edit, Glob, Grep, Bash
type: tool
domain: engineering
---

# HarmonyOS Auto Build & Fix

Automatically build a HarmonyOS NEXT project from the command line, parse compile errors, fix them, and retry — repeating until the build succeeds.

## Inputs

Declare all inputs up front as explicit `snake_case` variables, bound to the positional `$ARGUMENTS`:

| Variable | Positional | Required | Meaning |
|---|---|---|---|
| `harmony_project_dir` | `$ARGUMENTS[0]` | yes | HarmonyOS project root, e.g. `D:/MyHmosApp` |
| `deveco_studio_path` | `$ARGUMENTS[1]` | no | DevEco Studio installation root, e.g. `D:/DevEco Studio`. When omitted, resolved automatically (see Step 0.2) |
| `signed` | `--signed` flag | no | **Assertion**, not a request: the caller requires a signed artifact. The build mode itself is detected from `build-profile.json5` (Step 0.5); this flag turns "project is not configured for signing" into a hard stop instead of a normal unsigned build |

Note: `--signed` is a positional-order-independent flag — when `deveco_studio_path` is omitted it may appear as `$ARGUMENTS[1]`, and when provided it appears as `$ARGUMENTS[2]`.

Pass `--signed` only when an unsigned artifact would be useless downstream (for example a package that must be installed on a device). For a plain compile gate, omit it and let the project's own configuration decide.

---

## Step 0: Validate Inputs & Setup Environment

1. **Verify project exists** — Check that `harmony_project_dir` contains a valid HarmonyOS project (look for `build-profile.json5`, `entry/src` directory, `oh-package.json5`).

2. **Resolve `<deveco-path>`** — follow the standard config chain, stopping at the first source that yields a valid path:
   1. `deveco_studio_path` if provided (and not `--signed`).
   2. **OS environment variables** (`echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows): `DEVECO_HOME` → parent of `DEVECO_SDK_HOME` → strip the trailing `/sdk/default/openharmony/ets` from `OHOS_SDK_PATH` (legacy).
   3. **`~/.hometrans/config.json`** — read `env.DEVECO_HOME`, else parent of `env.DEVECO_SDK_HOME` (written by `ht init`).
   4. **Ask the user** for the DevEco Studio install path (suggest running `ht init` to persist it as an environment variable).

   **Verify the resolved `<deveco-path>`** contains:
   - `tools/node/node.exe`
   - `tools/hvigor/bin/hvigorw.js`
   - `tools/ohpm/bin/ohpm`
   - `sdk/` directory

   If verification fails for an env-sourced path, treat that source as invalid and continue down the list; if it fails for a user-supplied path, report what is missing and ask again.

3. **Set up `local.properties`** — Ensure the project root has `local.properties` with:
   ```properties
   hwsdk.dir=<deveco-path>/sdk
   ```
   Create it if missing. Use forward slashes in the path.

4. **Use `npx --yes devecocli build` as the build entrypoint** — The build-fix loop should call `npx --yes devecocli build` rather than invoking `ohpm` or `hvigorw.js` directly. `npx --yes devecocli build` wraps dependency install (`ohpm install`) and the underlying hvigor build.

5. **Determine Build Mode** — read it from the project; callers do not choose it. Open `build-profile.json5` and inspect `app.signingConfigs` together with the target product's `signingConfig` reference:
   - **Configured for signing** (`app.signingConfigs` has at least one entry **and** the product being built references it) → **signed build**. Go to Step 0.5.
   - **Otherwise** → **unsigned build**. Go straight to Step 1.

   Leave `build-profile.json5` exactly as it is either way; `signingConfigs` is touched only by the signing-error branch in Step 1.4, which restores it.

   `devecocli build` exposes only `--product` / `--modules` / `--build-mode`; it has no signing switch. Signing is governed entirely by the product entry's `signingConfig` reference in `build-profile.json5`, which is why the mode is read from there rather than passed in.

6. **Apply the `--signed` assertion, if present** — `--signed` does not *request* signing; it asserts that the caller **requires** a signed artifact:
   - Detection says **signed** → nothing changes; Step 0.5 runs as usual, and its checks are hard failures.
   - Detection says **unsigned** → **STOP and report** that the project has no usable signing configuration, naming what is missing (`app.signingConfigs` absent/empty, or the product carries no `signingConfig` reference). Point the user at DevEco Studio → **File → Project Structure → Signing Configs**.

   Without the assertion, an unsigned outcome is a normal result, not an error.

---

## Step 0.5: Validate Signing Config (signed builds only)

Runs when Step 0 detected a **signed build**. Skipped entirely for unsigned builds.

Signing information is read directly from the project's own `build-profile.json5`; the user configures it in DevEco Studio.

**How a failure here is handled depends on the `--signed` assertion:**

- **Asserted** → any check below that fails **STOPs the run** and reports to the user.
- **Not asserted** → record the finding, **do not stop**, and continue to Step 1. A build that then fails on the broken signing configuration is picked up by the signing-error branch in Step 1.4.

### Steps:

Reaching this step already established that `app.signingConfigs` has an entry and the target product references it — that is what Step 0 detected on. What remains is whether the referenced material is actually usable.

1. **Validate signing material files exist** — for the `signingConfigs` entry the product references, check that the files named by `material.certpath`, `material.storeFile`, and `material.profile` exist on disk.
   - **All present** → proceed to Step 1.
   - **Any missing, and `--signed` was asserted** → **STOP and report** which files are missing:
     > Signing material referenced by `build-profile.json5` is missing: `<list>`.
     > Please open the project in DevEco Studio, go to **File → Project Structure → Signing Configs**, re-generate the signing config, then re-run.
   - **Any missing, no assertion** → record the missing files, proceed to Step 1, and let the signing-error branch in Step 1.4 handle the build failure if one occurs.

2. Proceed to Step 1.

---

## Step 1: Build-Fix Loop

Execute the following loop. **Maximum 20 iterations** to prevent infinite loops.

> **`build-profile.json5` is caller state.** The only step allowed to modify it is the signing-error branch in 1.4, and that branch restores it before this skill returns — on success, on failure, and on hitting the iteration cap alike. Never leave the project's signing configuration altered.

### 1.1 Run CLI Build

Use `npx --yes devecocli build` as the build command inside the fix loop. It is the supported wrapper for dependency install and hvigor execution, so the skill should not instruct raw `hvigorw.js` calls.

1. **Invoke `npx --yes devecocli build` from the project root**:

   ```bash
   cd "<harmony_project_dir>"
   npx --yes devecocli build
   ```
   If the project needs an explicit module target, prefer:
   ```bash
   cd "<harmony_project_dir>"
   npx --yes devecocli build --modules entry
   ```

   The command is the same in both build modes — the CLI has no signing switch. In signed mode, run it only after Step 0.5 verifies the signing config in `build-profile.json5`. Do not bypass the CLI wrapper.

2. **Capture the full output** into a variable.

- The build command may take 1-3 minutes. Use a timeout of 300000ms (5 minutes).

### 1.2 Check Build Result

- If output contains `BUILD SUCCESSFUL` → **Build succeeded!** Exit the loop, go to Step 2.
- If output contains `ERROR` or `BUILD FAILED` → Parse errors and continue to 1.3.

### 1.3 Parse Errors

Extract error information from the build output. Errors typically appear in these formats:

```
ERROR: <file-path>:<line>:<col> - <error-code>: <message>
```

or

```
ArkTS:ERROR File: <file-path>:<line>:<col>
  <error message>
```

Group errors by file. Focus on **actual errors**, not warnings.

### 1.4 Fix Errors

Read each file that has errors and apply fixes.

#### Signing-config errors (unsigned build mode only)

Applies when **`--signed` was NOT passed** and the build failed because the product's `signingConfig` reference cannot be satisfied — for example the `material` files (`certpath` / `storeFile` / `profile`) named in `build-profile.json5` do not exist on disk. In signed mode this case is already caught by Step 0.5 and stops the run; here the goal is only to get the code to compile.

1. **Back up** `build-profile.json5` to `build-profile.json5.hmos-bak` in the project root before the first edit. Create the backup exactly once per invocation.
2. **Remove only the `signingConfig` reference** from the `products` entry being built. **Keep the `app.signingConfigs` definitions themselves untouched** — they are the project's own configuration.
3. Rebuild. Treat any further failure as an ordinary compile error and continue the loop.
4. **Restore** `build-profile.json5` from the backup and delete the backup file **before this skill returns** — on success (Step 2), on give-up, and on hitting the 20-iteration cap. Then record one line in the report: `signingConfig reference temporarily removed to complete compilation; build-profile.json5 restored`.

Any other reason to edit `build-profile.json5` follows the same back-up / restore rule.

#### Compile errors

Use the error reference table below to identify and fix common issues:

| Error Code / Pattern | Message | Fix |
|---|---|---|
| `arkts-limited-throw` | "throw statements cannot accept values of arbitrary types" | Change `throw err` to `throw (err instanceof Error) ? err : new Error(String(err))` |
| `arkts-no-obj-literals-as-types` | "Object literals cannot be used as type declarations" | Define a named `interface` instead of inline `{ key: Type }` |
| `arkts-no-untyped-obj-literals` | "Object literal must correspond to some explicitly declared class or interface" | Assign to typed variable: `const r: MyInterface = {...}; return r;` |
| `arkts-no-any-type` / `any` type usage | "Use explicit types instead of any" | Replace `any` with the correct concrete type or `object` |
| `arkts-no-var` | "Use 'let' or 'const' instead of 'var'" | Replace `var` with `let` or `const` |
| `10903329` | "Unknown resource name 'xxx'" | Verify resource exists in `resources/base/media/` or `element/*.json`. Use `layered_image` as fallback for missing images. **Special case**: `$r('sys.media.ohos_ic_public_xxx')` references system icons by SDK-specific names that may not exist in the build SDK — replace with `$r('app.media.ic_public_xxx')` and add the icon file to `resources/base/media/` |
| `10505001` | "Resource[] is not assignable to ResourceColor" | Remove array brackets: `.fontColor($r('app.color.x'))` not `.fontColor([$r('app.color.x')])` |
| `00303221` | "permission must be a value that is predefined within the SDK" | Remove invalid permission from `module.json5`. See valid permissions list below |
| Missing import | "Cannot find name 'xxx'" | Add the correct import (see import reference below) |
| Missing `async` | "await expression requires async function" | Add `async` to the enclosing function |
| Missing `build()` | "@Component must have build() method" | Add a `build() {}` method to the @Component struct |
| Type mismatch | Various type errors | Fix the type annotation or cast appropriately |
| Duplicate identifier | "Duplicate identifier 'xxx'" | Remove or rename the duplicate declaration |

**For errors NOT in the table above**: Read the error message carefully, read the relevant source file, understand the context, and apply an appropriate fix. Use your knowledge of ArkTS/HarmonyOS to determine the correct solution.

### 1.5 Log Progress

After each fix iteration, briefly report:
- Iteration number
- Number of errors found
- Summary of fixes applied
- Whether re-building

Then go back to **1.1** and rebuild.

---

## Step 2: Build Success Report

Before presenting the summary, restore `build-profile.json5` if the signing-error branch in 1.4 backed it up.

Then present:

1. **Build Status**: SUCCESS
2. **Output HAP Path**: list the `.hap` files actually present in `<project>/entry/build/default/outputs/default/` — do **not** infer the filename from the `--signed` flag. A project whose product references a valid `signingConfig` emits `entry-default-signed.hap` regardless of the flag; one without emits `entry-default-unsigned.hap`.
3. **Build Type**: Signed HAP or Unsigned HAP — read this off the filename found in step 2. State the mode detected in Step 0 alongside it (`signed — product references <config>` / `unsigned — project has no signing config`), so a caller expecting a signed package can tell that this project simply is not configured for one.
4. **Signing** (when a signed HAP was produced): name the signing config from `build-profile.json5` that was used.
5. **`build-profile.json5`**: `untouched`, or `signingConfig reference temporarily removed and restored`.
6. **Iterations**: How many build-fix cycles were needed
7. **Total Errors Fixed**: Count of errors fixed across all iterations
8. **Summary of Changes**: List of files modified and what was fixed in each

---

## Reference: Common HarmonyOS Imports

```typescript
// Network
import { http } from '@kit.NetworkKit';

// Data persistence
import { preferences } from '@kit.ArkData';
import { relationalStore } from '@kit.ArkData';

// UI utilities
import { router } from '@kit.ArkUI';
import { promptAction } from '@kit.ArkUI';

// Ability & Context
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { common } from '@kit.AbilityKit';

// File I/O
import { fileIo } from '@kit.CoreFileKit';

// Logging
import { hilog } from '@kit.PerformanceAnalysisKit';

// JSON parsing — built-in, no import needed
// ArkUI built-in components (Text, Column, Row, List, Button, Image, etc.) — NO import needed
```

## Reference: Valid Permission Names

Commonly used SDK-validated permissions for `module.json5`:

- `ohos.permission.INTERNET`
- `ohos.permission.GET_NETWORK_INFO`
- `ohos.permission.GET_WIFI_INFO`
- `ohos.permission.KEEP_BACKGROUND_RUNNING`
- `ohos.permission.PUBLISH_AGENT_REMINDER`
- `ohos.permission.CAMERA`
- `ohos.permission.MICROPHONE`
- `ohos.permission.APPROXIMATELY_LOCATION`
- `ohos.permission.LOCATION`
- `ohos.permission.READ_MEDIA`
- `ohos.permission.WRITE_MEDIA`
- `ohos.permission.USE_BLUETOOTH`
- `ohos.permission.VIBRATE`

**Note**: `ohos.permission.NOTIFICATION` does NOT exist. When in doubt, omit the permission.

## Reference: ArkTS Strict Mode Rules

All code must comply with ArkTS strict mode:

1. **No `any` type** — Use explicit types or `object`
2. **No `var`** — Only `let` and `const`
3. **No dynamic property access** — Use typed interfaces instead of `obj['key']` on typed objects
4. **`throw` must throw Error instances** — Never `throw 'string'` or `throw unknownVar`
5. **All object literals must match declared interfaces** — No anonymous `{ key: val }` returns without a matching interface
6. **No inline object literal types** — `function(): { a: string }` is forbidden; define a named `interface`
7. **All `@Component` structs must have `build()`** — Missing build method is a compile error
8. **`$r()` resource references validated at compile time** — All referenced resources must exist
9. **`fontColor()` expects `ResourceColor`**, not `Resource[]` — Don't wrap in array brackets (exception: `SymbolGlyph`)
10. **Permission names in `module.json5`** — Must be SDK-predefined values

## Important Notes

- **Timeout**: Individual build commands may take up to 5 minutes. Use a 300000ms timeout.
- **Max iterations**: Stop after 20 iterations to prevent infinite loops. If build still fails after 20 attempts, restore `build-profile.json5` per 1.4 and report the remaining errors to the user.
- **`build-profile.json5` is caller state**: leave it byte-identical to how it arrived. The signing-error branch in 1.4 is the only path allowed to edit it, and it restores it on every exit path.
- **Don't over-fix**: Only fix errors reported by the compiler. Don't proactively refactor unrelated code.
- **Read before edit**: Always read a file before modifying it. Understand the surrounding context.
- **One error can cause many**: A single root-cause fix (like adding a missing interface) may resolve multiple reported errors. After fixing root causes, rebuild to see remaining issues.
- **ohpm errors**: If the build fails because of missing packages, rerun `npx --yes devecocli build` so it can retry dependency install before you investigate deeper.

---

## References

- `references/arkts-strict-patterns.md` — ArkTS 严格模式编译错误的确定性修复 Pattern（throw/any/var/interface 等）
- `references/known-patterns.md` — 已知常见编译错误 Pattern 及修复方案
- `references/rdb-entity-pattern.md` — RDB 实体类编译错误 Pattern（数据库实体相关）
