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 scripts/lib/github-discussions.js when the gh command exited zero but its stdout was not valid JSON. The wrapper expects gh api --jq or graphql output to be parseable; anything else (text, HTML, empty) triggers this error.
Source
Thrown at scripts/lib/github-discussions.js:51
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 discussionNeedsMaintainerTouch(discussion) {
if (MAINTAINER_ASSOCIATIONS.has(discussion.authorAssociation)) {
return false;
}
if (
discussion.answer
&& MAINTAINER_ASSOCIATIONS.has(discussion.answer.authorAssociation)
) {
return false;
}
const comments = discussion.comments && Array.isArray(discussion.comments.nodes)
? discussion.comments.nodes
: [];View on GitHub (pinned to 01e15490f0)
Solutions
- Reproduce manually: gh api graphql -f query=... --jq '.' and inspect the raw stdout.
- If gh prints warnings on stdout, upgrade gh or redirect stdout filtering; ensure logs go to stderr only.
- If using ECC_GH_SHIM, ensure it does console.log(JSON.stringify(payload)) and writes any diagnostics to stderr.
- Upgrade gh CLI to a recent stable release.
Example fix
// before
return JSON.parse(stdout || 'null');
// after (defensive trim of leading non-JSON)
const trimmed = String(stdout || '').trim();
if (!trimmed || trimmed[0] !== '{' && trimmed[0] !== '[' && trimmed !== 'null') {
throw new Error(`gh ${args.join(' ')} returned non-JSON output: ${trimmed.slice(0, 200)}`);
}
return JSON.parse(trimmed); Defensive patterns
Strategy: validation
Validate before calling
function looksJson(s) {
const t = String(s || '').trim();
return t === 'null' || t.startsWith('{') || t.startsWith('[');
}
const stdout = runCommand(cmd, args, opts);
if (!looksJson(stdout)) {
throw new Error(`Expected JSON from ${cmd}; got: ${stdout.slice(0, 200)}`);
} Try / catch
try {
return JSON.parse(stdout || 'null');
} catch (err) {
if (/Unexpected token|JSON/.test(err.message)) {
throw new Error(`gh ${args.join(' ')} returned non-JSON: ${stdout.slice(0, 200)}`);
}
throw err;
} Prevention
- Pin gh CLI to a stable version that emits machine-readable JSON only.
- If writing a shim (ECC_GH_SHIM), always JSON.stringify the result and log to stderr.
- Add a smoke test that runs the exact gh query and asserts JSON.parse works.
When it happens
Trigger: runGhJson calls runCommand successfully (status 0), then JSON.parse(stdout || 'null') throws. Happens when gh prints a human-readable message, a warning interleaved with JSON, an empty string with garbage, or when ECC_GH_SHIM emits non-JSON stdout.
Common situations: gh CLI version prints a deprecation notice on stdout before the JSON; shim script forgets to JSON.stringify its result; wrong gh subcommand returned plain text; paging footer or 'Processing...' line leaked into stdout; gh wrote logs to stdout instead of stderr.
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
- gh ${args.join(' ')} returned invalid JSON: ${error.message}
- Malformed coordination JSON in body: ${error.message} — raw:
- Failed to load policy from ${resolvedPath}: ${error.message}
- ${command} ${args.join(' ')} failed: ${result.error.message}
- ${command} ${args.join(' ')} failed: ${(result.stderr || res
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/6d18975b20edcfcf.
Report an issue: GitHub.