Hmbown/CodeWhale · error · Error
GitHub Release ${tag} returned invalid JSON: ${error.message
Error message
GitHub Release ${tag} returned invalid JSON: ${error.message} What it means
The gh api subprocess exited 0 but its stdout could not be JSON.parsed. The script expects the raw REST release object on stdout, so any pollution there — a gh warning, an active pager, a GH_BIN wrapper that reformats output — breaks parsing. The underlying SyntaxError message is appended to name where parsing failed.
Source
Thrown at scripts/release/ensure-release-assets-absent.js:49
output = exec(ghBin, ["api", endpoint], {
encoding: "utf8",
maxBuffer: 10 * 1024 * 1024,
stdio: ["ignore", "pipe", "pipe"],
});
} catch (error) {
if (isNotFoundError(error)) {
return null;
}
const detail = String(error && error.stderr ? error.stderr : "").trim();
throw new Error(
`Could not inspect GitHub Release ${tag}${detail ? `: ${detail}` : ""}`,
);
}
try {
return JSON.parse(output);
} catch (error) {
throw new Error(`GitHub Release ${tag} returned invalid JSON: ${error.message}`);
}
}
function assertReleaseAssetsAbsent(release, tag) {
if (release === null) {
return;
}
if (!release || !Array.isArray(release.assets)) {
throw new Error(`GitHub Release ${tag} did not provide an asset inventory`);
}
if (release.assets.length === 0) {
return;
}
const names = release.assets.map((asset) => asset && asset.name).filter(Boolean);
const inventory = names.length > 0 ? names.join(", ") : `${release.assets.length} unnamed asset(s)`;
throw new Error(
`Refusing to replace existing assets for ${tag}: ${inventory}. ` +View on GitHub (pinned to 8880682c63)
Solutions
- Reproduce manually: gh api repos/OWNER/REPO/releases/tags/vX.Y.Z | head -c 200 to see what stdout actually contains
- Unset GH_PAGER for the step (env -u GH_PAGER ...) and disable gh output formatting
- If GH_BIN is a wrapper, keep stdout pristine — send any diagnostics to stderr only
Example fix
# before: GH_PAGER=less pollutes stdout -> invalid JSON # after env -u GH_PAGER GH_BIN=gh node scripts/release/ensure-release-assets-absent.js acme/codewhale v1.2.3
Defensive patterns
Strategy: validation
Validate before calling
const { execFileSync } = require("node:child_process");
function ghStdoutIsCleanJson() {
const probe = execFileSync(process.env.GH_BIN || "gh", ["api", "rate_limit"], { encoding: "utf8" });
try {
JSON.parse(probe);
return true;
} catch {
return false;
}
} Prevention
- Unset GH_PAGER for CI steps that shell out to gh
- Keep GH_BIN pointing at the plain gh binary; wrappers must send diagnostics to stderr only
- Add the rate_limit JSON probe as a workflow preflight so stdout pollution fails early with a clear cause
When it happens
Trigger: GH_PAGER or gh config writing to stdout in CI; GH_BIN pointing at a wrapper (jq filter, tee, formatter); gh emitting a notice line before the JSON; a proxy injecting a banner into piped output.
Common situations: Custom GH_BIN shims inside the workflow; environment-wide pager settings; global gh config changed output formatting; test harnesses stubbing gh with echo.
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
- Invalid GitHub repository: ${repo}
- Could not inspect GitHub Release ${tag}: ${detail}
- GitHub Release ${tag} did not provide an asset inventory
- Usage: node scripts/release/ensure-release-assets-absent.js
- Usage: node scripts/release/assemble-release-assets.js INP
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/ea6e4f13e977efba.
Report an issue: GitHub.