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
- Inspect the raw stdout (log runGh output before parsing) to see what non-JSON text was returned.
- Ensure every gh call that feeds runGhJson includes the --json <fields> flag.
- If using ECC_GH_SHIM, make the shim emit valid JSON on stdout and warnings on stderr only.
- 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
- Always pass --json <fields> to gh calls feeding runGhJson.
- Keep shim output strictly JSON on stdout; send notices to stderr.
- Pin a known gh version and test its JSON output shape.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed coordination JSON in body: ${error.message} — raw:
- Failed to load policy from ${resolvedPath}: ${error.message}
- Invalid repo format: "${repo}". Expected "owner/repo".
- Invalid issue number: ${value}
- ${command} ${args.join(' ')} failed: ${result.error.message}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/1a948fa93a8285f0.
Report an issue: GitHub.