{"record":{"id":"e7c10878c6379f86","repo":"Hmbown/CodeWhale","slug":"could-not-inspect-github-release-tag-detail","errorCode":null,"errorMessage":"Could not inspect GitHub Release ${tag}: ${detail}","messagePattern":"Could not inspect GitHub Release (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/release/ensure-release-assets-absent.js","lineNumber":41,"sourceCode":"  );\n}\n\nfunction fetchRelease(repo, tag, ghBin = process.env.GH_BIN || \"gh\", exec = execFileSync) {\n  validateTarget(repo, tag);\n  const endpoint = `repos/${repo}/releases/tags/${encodeURIComponent(tag)}`;\n  let output;\n  try {\n    output = exec(ghBin, [\"api\", endpoint], {\n      encoding: \"utf8\",\n      maxBuffer: 10 * 1024 * 1024,\n      stdio: [\"ignore\", \"pipe\", \"pipe\"],\n    });\n  } catch (error) {\n    if (isNotFoundError(error)) {\n      return null;\n    }\n    const detail = String(error && error.stderr ? error.stderr : \"\").trim();\n    throw new Error(\n      `Could not inspect GitHub Release ${tag}${detail ? `: ${detail}` : \"\"}`,\n    );\n  }\n\n  try {\n    return JSON.parse(output);\n  } catch (error) {\n    throw new Error(`GitHub Release ${tag} returned invalid JSON: ${error.message}`);\n  }\n}\n\nfunction assertReleaseAssetsAbsent(release, tag) {\n  if (release === null) {\n    return;\n  }\n  if (!release || !Array.isArray(release.assets)) {\n    throw new Error(`GitHub Release ${tag} did not provide an asset inventory`);\n  }","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/scripts/release/ensure-release-assets-absent.js#L23-L59","documentation":"The `gh api repos/<repo>/releases/tags/<tag>` subprocess failed with something other than an HTTP 404 (404 means 'no release yet' and returns null). The trimmed stderr is appended as detail. Typical causes: expired or missing gh authentication (401), rate limiting (403), network failure, or gh not installed / GH_BIN mispointed (which yields an error with empty stderr and no detail).","triggerScenarios":"GH_TOKEN/GITHUB_TOKEN expired or lacking repo scope; secondary rate limit during heavy CI; runner offline or DNS broken; GH_BIN unset on an image without gh; GH_BIN pointing at a wrapper that exits non-zero.","commonSituations":"Token not passed to the release-guard step; a rotated PAT; a workflow burst hitting rate limits; a minimal container image missing the gh binary.","solutions":["Run `gh auth status` with the same token environment to confirm authentication and scopes","Read the appended stderr detail — it contains gh's actual HTTP error line","If rate-limited or network-blipped, wait out the window and re-run the script (it is idempotent; 404/absent is the success path)","Confirm gh is installed and GH_BIN (if set) points to the real gh binary"],"exampleFix":"# before: token missing -> Could not inspect GitHub Release v1.2.3: HTTP 401\nnode scripts/release/ensure-release-assets-absent.js acme/codewhale v1.2.3\n\n# after\nGH_TOKEN=\"$RELEASE_GUARD_TOKEN\" node scripts/release/ensure-release-assets-absent.js acme/codewhale v1.2.3","handlingStrategy":"retry","validationCode":"const { execFileSync } = require(\"node:child_process\");\nfunction ghEnvironmentIsReady() {\n  try {\n    execFileSync(process.env.GH_BIN || \"gh\", [\"auth\", \"status\"], { stdio: \"ignore\" });\n    return true;\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"const { fetchRelease } = require(\"./scripts/release/ensure-release-assets-absent\");\nasync function inspectReleaseWithRetry(repo, tag, attempts = 3) {\n  for (let attempt = 0; ; attempt++) {\n    try {\n      return fetchRelease(repo, tag);\n    } catch (error) {\n      const transient = /HTTP 5\\d\\d|rate limit|ETIMEDOUT|ENOTFOUND|ECONNRESET/i.test(String(error.message));\n      if (!transient || attempt >= attempts - 1) throw error;\n      await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 5000));\n    }\n  }\n}","preventionTips":["Verify gh authentication (gh auth status) in a preflight step of the release workflow","Pass a token with the minimum needed scopes and rotate it before expiry, not after failures","Treat HTTP 4xx as non-retryable — only 5xx, rate limits, and network errors are worth another attempt","The script is idempotent: a re-run after a transient failure is always safe (404 is the success path)"],"tags":["github","cli","network","auth","release"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}