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
- Run the exact gh command manually and inspect stdout — anything before the JSON must move to stderr.
- If using ECC_GH_SHIM, ensure it writes only JSON to stdout and all logs to stderr.
- Upgrade or pin gh to a version whose `--json` output is stable; check `gh --version`.
- 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
- Keep all shim logging on stderr; stdout must carry JSON only.
- Pin gh to a version with stable --json output.
- Unset ECC_GH_SHIM to test against real gh when diagnosing.
- Inspect raw stdout manually when this fires to find the polluting text.
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
- 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}
- ${command} ${args.join(' ')} failed: ${(result.stderr || res
- ECC_PROJECT_DIR must be a child path within /workspace.
- Unknown argument: ${arg}
- ${source} is not valid JSON: ${error.message}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/831627542835c775.
Report an issue: GitHub.