affaan-m/ECC · error · Error

gh ${args.join(' ')} returned invalid JSON: ${error.message}

Error message

gh ${args.join(' ')} returned invalid JSON: ${error.message}

What it means

Thrown by runGhJson in platform-audit when a `gh ... --json ...` call succeeds (exit 0) but its stdout is not valid JSON. The wrapper expects gh's structured output; a parse failure means gh emitted something unexpected (warnings on stdout, a non-JSON error page, a shim that logged prose). The raw JSON.parse error message is appended for diagnosis.

Source

Thrown at scripts/platform-audit.js:258

  return result.stdout || '';
}

function runGhJson(args, options = {}) {
  const shimPath = process.env.ECC_GH_SHIM;
  const command = shimPath ? process.execPath : 'gh';
  const commandArgs = shimPath ? [shimPath, ...args] : args;
  const env = { ...process.env };

  if (!options.useEnvGithubToken) {
    delete env.GITHUB_TOKEN;
  }

  const stdout = runCommand(command, commandArgs, { env });
  try {
    return JSON.parse(stdout || 'null');
  } catch (error) {
    throw new Error(`gh ${args.join(' ')} returned invalid JSON: ${error.message}`);
  }
}

function readText(rootDir, relativePath) {
  try {
    return fs.readFileSync(path.join(rootDir, relativePath), 'utf8');
  } catch (_error) {
    return '';
  }
}

function fileExists(rootDir, relativePath) {
  return fs.existsSync(path.join(rootDir, relativePath));
}

function safeParseJson(text) {
  if (!text || !text.trim()) {
    return null;

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Run the exact gh command manually and inspect stdout — anything before the JSON must move to stderr.
  2. If using ECC_GH_SHIM, ensure it writes only JSON to stdout and all logs to stderr.
  3. Upgrade or pin gh to a version whose `--json` output is stable; check `gh --version`.
  4. Unset ECC_GH_SHIM to use the real gh binary and rule out the shim.

Example fix

// before — shim logs to stdout, breaking JSON.parse
const stdout = runCommand(command, commandArgs, { env });
return JSON.parse(stdout || 'null');
// after — shim must log to stderr; meanwhile strip leading non-JSON lines
const stdout = runCommand(command, commandArgs, { env });
const jsonStart = stdout.indexOf(/^\{|^\[/m.test(stdout) ? stdout.search(/[{[]/) : 0);
return JSON.parse(stdout.slice(jsonStart) || 'null');
Defensive patterns

Strategy: try-catch

Validate before calling

// Detect whether stdout is JSON-shaped before parsing
function looksLikeJson(stdout) {
  const s = String(stdout || '').trim();
  return s.startsWith('{') || s.startsWith('[');
}

Type guard

function isJsonObjectOrArray(value) {
  return value !== null && typeof value === 'object';
}

Try / catch

let data;
try {
  data = JSON.parse(stdout || 'null');
} catch (error) {
  throw new Error(`gh ${args.join(' ')} returned invalid JSON: ${error.message}`);
}
return data;

Prevention

When it happens

Trigger: A custom ECC_GH_SHIM prints logs to stdout instead of stderr; gh emits a deprecation/rate-limit notice before the JSON; gh version returns a different shape; stdout buffering concatenated two responses; the shim exits 0 but wrote text.

Common situations: Shim logging misconfigured to stdout; gh CLI upgraded and changed output; a proxy/wrapper altered the stream; locale/encoding inserted a BOM or CRLF.

Understand the failure class

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/831627542835c775. Report an issue: GitHub.