moeru-ai/airi · error · TypeError

Expected `cap run --list --json` to return a JSON array.

Error message

Expected `cap run --list --json` to return a JSON array.

What it means

To auto-select a device, cap-vite runs `cap run <platform> --list --json` and expects stdout to be a JSON array; parseCapacitorTargetList throws this TypeError when JSON.parse succeeded but the top-level value is not an array (e.g. an object payload). Note that non-JSON stdout instead surfaces as a SyntaxError from JSON.parse itself.

Solutions

  1. Run `npx cap run ios --list --json` yourself and inspect the actual stdout shape
  2. Align the project's @capacitor/cli version with the version cap-vite depends on (or update cap-vite)
  3. Ensure the project-local cap binary is the one resolved (npx from the project root), not a stale global install
  4. If output shape legitimately changed, report it upstream to cap-vite with the captured JSON

Example fix

# before
# trust `cap run ios --list --json` output shape blindly

# after
$ npx cap run ios --list --json   # inspect stdout; it must be a JSON array like [{"id":"...","name":"..."}]
$ pnpm update @capacitor/cli      # align versions if the shape differs
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-check the CLI output shape before trusting it
const output = await x('cap', ['run', platform, '--list', '--json'])
const trimmed = output.stdout.trim()
if (!trimmed.startsWith('['))
  throw new Error(`cap --list did not emit a JSON array: ${trimmed.slice(0, 200)}`)

Type guard

function isCapacitorTargetList(v: unknown): v is Array<{ id: string }> {
  return Array.isArray(v) && v.every(t => typeof t === 'object' && t !== null && typeof (t as { id?: unknown }).id === 'string')
}

Try / catch

try {
  const targets = await listCapacitorTargets(platform)
}
catch (err) {
  if (err instanceof TypeError || err instanceof SyntaxError) {
    // inspect actual `npx cap run <platform> --list --json` output; likely a CLI version mismatch
    logCapListOutput(platform)
  }
  throw err
}

Prevention

When it happens

Trigger: An @capacitor/cli version whose --list --json emits an object or differently shaped payload; stdout polluted by extra log output that still parses as JSON but not as an array; a wrapper `cap` binary printing its own structure.

Common situations: Version drift between the locally installed @capacitor/cli and what cap-vite was built against; a global `cap` shadowing the project one via npx/PATH; plugin logs routed to stdout in CI.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/1dcf1946debb361e. Report an issue: GitHub.

Appendix: source

Thrown at packages/cap-vite/src/native.ts:98

    ['app', 'src', 'main', 'res', 'xml', 'config.xml'],
    ['app', 'capacitor.build.gradle'],
    ['capacitor-cordova-android-plugins'],
    ['capacitor.settings.gradle'],
  ],
}

export function parseCapacitorPlatform(value: string | undefined): CapacitorPlatform | null {
  return value === 'android' || value === 'ios' ? value : null
}

export function hasCapacitorTargetArg(capArgs: string[]): boolean {
  return capArgs.some((arg, index) => arg === '--target' || (index > 0 && arg.startsWith('--target=')))
}

function parseCapacitorTargetList(value: string): CapacitorTarget[] {
  const parsed = JSON.parse(value)
  if (!Array.isArray(parsed)) {
    throw new TypeError('Expected `cap run --list --json` to return a JSON array.')
  }

  return parsed
    .filter((target): target is CapacitorTarget => typeof target === 'object' && target !== null && typeof (target as CapacitorTarget).id === 'string')
}

async function listCapacitorTargets(platform: CapacitorPlatform): Promise<CapacitorTarget[]> {
  const output = await x('cap', ['run', platform, '--list', '--json'])

  return parseCapacitorTargetList(output.stdout)
}

/**
 * Resolves Capacitor run arguments by applying target defaults.
 *
 * Use when:
 * - `cap-vite` is about to run `cap run`
 * - Callers want env-based device IDs before falling back to the first available target

View on GitHub (pinned to 677329427f)