{"record":{"id":"87d716b342f2c1d8","repo":"pbakaus/impeccable","slug":"failed-to-parse-gh-json-output-err-message","errorCode":null,"errorMessage":"Failed to parse gh JSON output: ${err.message}","messagePattern":"Failed to parse gh JSON output: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/github/sheriff.mjs","lineNumber":774,"sourceCode":"  return new Set(logins.map(normalizeLogin).filter(Boolean));\n}\n\nfunction normalizeLogin(login) {\n  return String(login || '').toLowerCase();\n}\n\nfunction requireValue(argv, index, flag) {\n  const value = argv[index];\n  if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value.`);\n  return value;\n}\n\nfunction runGhJson(args) {\n  const result = runGh(args, { quiet: true });\n  try {\n    return JSON.parse(result.stdout || '{}');\n  } catch (err) {\n    throw new Error(`Failed to parse gh JSON output: ${err.message}`);\n  }\n}\n\nfunction runGh(args, options = {}) {\n  const result = spawnSync('gh', args, {\n    encoding: 'utf-8',\n    env: process.env,\n  });\n  if (!options.quiet && result.stdout) process.stdout.write(result.stdout);\n  if (!options.quiet && result.stderr) process.stderr.write(result.stderr);\n  if (result.error) throw result.error;\n  if (result.status !== 0 && !options.allowFailure) {\n    throw new Error(`gh ${args.join(' ')} failed with exit ${result.status}: ${result.stderr || result.stdout}`);\n  }\n  return result;\n}\n\nfunction printHelp() {","sourceCodeStart":756,"sourceCodeEnd":792,"githubUrl":"https://github.com/pbakaus/impeccable/blob/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/scripts/github/sheriff.mjs#L756-L792","documentation":"Thrown by runGhJson in the GitHub sheriff script after the `gh` CLI exited successfully (status 0) but emitted stdout that is not valid JSON. runGhJson runs `gh`, then calls JSON.parse(result.stdout || '{}'); any SyntaxError from parse is rewrapped with this message. Because runGh already validated a zero exit code, this specifically means gh produced non-JSON text despite succeeding.","triggerScenarios":"Calling runGhJson with gh args that do not request JSON output (missing --json <fields>), gh prepending an auth/network warning to stdout, a `gh` version that prints a deprecation notice on stdout, a transparent proxy or GH_HOST override returning an HTML error page with a 200, or stdout being empty/non-text.","commonSituations":"gh auth warnings leaking onto stdout instead of stderr; a gh subcommand that defaults to human-readable tables when no --json flag is passed; GitHub Enterprise returning an interstitial page; a CI runner with a stale gh version whose output format changed between minor releases.","solutions":["Inspect the raw stdout: temporarily log result.stdout before the JSON.parse to see exactly what gh returned.","Confirm every gh call routed through runGhJson passes an explicit --json <field,...> flag so gh emits machine-readable output.","Run `gh auth status` and `gh api user` to verify auth is healthy and stderr (not stdout) carries warnings.","Pin or upgrade gh to a known version and check the GitHub CLI changelog for output-format changes.","If a proxy/Enterprise host is involved, verify GH_HOST and HTTP(S)_PROXY env vars are not redirecting stdout."],"exampleFix":"// before\nfunction runGhJson(args) {\n  const result = runGh(args, { quiet: true });\n  return JSON.parse(result.stdout || '{}');\n}\n\n// after: assert JSON was requested and surface the raw payload on failure\nfunction runGhJson(args) {\n  if (!args.some(a => a === '--json' || a.startsWith('--jq'))) {\n    throw new Error(`runGhJson requires a --json flag; got: ${args.join(' ')}`);\n  }\n  const result = runGh(args, { quiet: true });\n  try {\n    return JSON.parse(result.stdout || '{}');\n  } catch (err) {\n    throw new Error(`Failed to parse gh JSON output: ${err.message} (raw stdout: ${String(result.stdout).slice(0, 200)})`);\n  }\n}","handlingStrategy":"validation","validationCode":"// Before calling runGhJson, assert the args request JSON and sniff the output.\nfunction assertJsonArgs(args) {\n  const i = args.indexOf('--json');\n  if (i === -1 || i === args.length - 1) {\n    throw new Error('runGhJson requires --json <fields>');\n  }\n}\n// After runGh, sanity-check stdout is object/array-shaped before parse:\nfunction looksJsony(s) {\n  const t = String(s ?? '').trimStart();\n  return t.startsWith('{') || t.startsWith('[');\n}","typeGuard":null,"tryCatchPattern":"// Catch parse failures distinctly from gh failures so each is diagnosable.\nfunction safeGhJson(args) {\n  let result;\n  try {\n    result = runGh(args, { quiet: true });\n  } catch (ghErr) {\n    throw new Error(`gh invocation failed: ${ghErr.message}`);\n  }\n  if (!looksJsony(result.stdout)) {\n    throw new Error(`gh returned non-JSON stdout: ${String(result.stdout).slice(0, 120)}`);\n  }\n  return JSON.parse(result.stdout);\n}","preventionTips":["Always pass --json <fields> to gh calls that feed JSON.parse.","Pin the gh CLI version in CI so output format cannot drift between runs.","Assert stdout starts with { or [ before parsing so a parse error becomes a diagnosable shape error.","Keep gh auth healthy with a preflight `gh auth status` before the sweep."],"tags":["github","json","cli","parsing","spawn"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}