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 when runGh succeeds (exit 0) but the stdout is not valid JSON. runGhJson wraps JSON.parse around the gh output; a parse failure means gh returned non-JSON text — usually a warning on stderr that leaked to stdout, a missing --json flag, a gh version that prints human text, or a shim returning wrong output.

Source

Thrown at scripts/lib/github-coordination/gh-api.js:78

// privileges.
function runGh(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.stripGithubToken) {
    delete env.GITHUB_TOKEN;
  }

  return runCommand(command, commandArgs, { cwd: options.cwd, env });
}

function runGhJson(args, options = {}) {
  try {
    return JSON.parse(runGh(args, options) || 'null');
  } catch (error) {
    throw new Error(`gh ${args.join(' ')} returned invalid JSON: ${error.message}`);
  }
}

function getIssue(repo, issueNumber, options = {}) {
  const { owner, name } = normalizeRepo(repo);
  const json = runGhJson([
    'issue',
    'view',
    String(issueNumber),
    '--repo',
    `${owner}/${name}`,
    '--json',
    'number,title,body,url,state,labels,author,updatedAt,assignees',
  ], options);

  if (!json) {
    throw new Error(`Unable to load issue #${issueNumber} from ${repo}`);
  }

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Inspect the raw stdout (log runGh output before parsing) to see what non-JSON text was returned.
  2. Ensure every gh call that feeds runGhJson includes the --json <fields> flag.
  3. If using ECC_GH_SHIM, make the shim emit valid JSON on stdout and warnings on stderr only.
  4. Pin a known-good gh version or add a version check, and separate stderr so notices don't corrupt stdout.

Example fix

// before
const out = runGhJson(['issue', 'view', String(n)]); // missing --json

// after — always pass --json and validate
const out = runGhJson(['issue', 'view', String(n), '--json', 'number,title,body']);
// inside runGhJson, surface the raw text on parse failure:
// catch (e) { throw new Error(`bad JSON: ${raw.slice(0,200)}`); }
Defensive patterns

Strategy: try-catch

Validate before calling

function looksJson(s) { const t = String(s || '').trim(); return t.startsWith('{') || t.startsWith('['); }
const raw = runGh(args);
if (!looksJson(raw)) {
  throw new Error(`gh did not return JSON (first 200 chars): ${raw.slice(0,200)}`);
}

Type guard

function isParsableJson(s) {
  try { JSON.parse(s); return true; } catch { return false; }
}

Try / catch

try {
  runGhJson(args);
} catch (e) {
  if (/invalid JSON/.test(e.message)) { console.error('gh output was not JSON — check the --json flag and shim output'); throw e; }
  throw e;
}

Prevention

When it happens

Trigger: A gh invocation missing the --json flag (so it prints a table); gh printing a deprecation/notice before the JSON; a shim (ECC_GH_SHIM) returning non-JSON; a gh version whose JSON output shape differs.

Common situations: gh auto-updated and changed output; a wrapper/alias injects text; the shim path is set but returns plain text; network proxy mangled the response; stderr redirect merged into stdout.

Understand the failure class

Related errors


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