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

Thrown by parseCapacitorTargetList() when `cap run <platform> --list --json` produces output that JSON.parses to something other than an array. The function is the internal parser for listCapacitorTargets(), which shells out to the Capacitor CLI expecting a JSON array of target descriptors.

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 27111382b4)

Solutions

  1. Run `npx cap run ios --list --json` (or android) manually and confirm it emits a JSON array to stdout.
  2. Upgrade or pin @capacitor/cli to a version known to support --list --json.
  3. Pass --target explicitly (or set CAPACITOR_DEVICE_ID_IOS / CAPACITOR_DEVICE_ID_ANDROID) to bypass the listTargets code path entirely.
  4. Ensure only the JSON payload goes to stdout; redirect any CLI banners/warnings to stderr.

Example fix

// before: relies on `cap run --list --json` shape
await runCapVite(viteArgs, ['ios'])
// after: bypass target discovery with an explicit device id
process.env.CAPACITOR_DEVICE_ID_IOS = deviceId
await runCapVite(viteArgs, ['ios'])
Defensive patterns

Strategy: validation

Validate before calling

// Validate shape before relying on listCapacitorTargets output
async function safeListTargets(platform: 'ios' | 'android') {
  const out = (await import('tinyexec').then(m => m.x('cap', ['run', platform, '--list', '--json']))).stdout
  let parsed: unknown
  try { parsed = JSON.parse(out) } catch { throw new Error('cap --list output was not JSON') }
  if (!Array.isArray(parsed)) throw new Error('cap --list did not return an array')
  return parsed.filter((t): t is { id: string } => t && typeof t.id === 'string')
}

Type guard

function isTargetList(value: unknown): value is { id: string }[] {
  return Array.isArray(value) && value.every(t => t !== null && typeof t === 'object' && typeof (t as any).id === 'string')
}

Try / catch

try {
  await runCapVite(viteArgs, ['ios'])
} catch (error) {
  if (error instanceof TypeError && error.message.includes('JSON array')) {
    console.error('Capacitor CLI did not return a target list. Check `cap run ios --list --json`.')
  }
}

Prevention

When it happens

Trigger: listCapacitorTargets(platform) is called (transitively from resolveCapRunArgs when no --target and no CAPACITOR_DEVICE_ID env is set), and the `cap` CLI emits a JSON object, a JSON scalar, or non-JSON text such as a warning, deprecation notice, or an error message routed to stdout.

Common situations: Capacitor CLI version mismatch where --list --json shape changed or is unsupported; `cap` not installed so the shell returns an error string; a newer/older @capacitor/cli that prints a banner before the JSON; CI environments where the CLI prints auth or telemetry noise to stdout.

Related errors


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