Problem Summary: An empty or malformed JSON error response can make `gh api` fail with only `unexpected end of JSON input`, hiding the HTTP status and GitHub request ID. This prevents distinguishing transient server failures from rejected requests in the review pipeline. Add a shared diagnostic helper for review startup, status synchronization and aggregation, helper downloads, submission, and head polling. Each failure records HTTP status, allowlisted response headers, bounded redacted response/output excerpts, exit code or timeout, and run context. Failure artifacts are uploaded even before PR checkout or when the caller handles the error. Load the helper from the immutable workflow commit before the first API request; distinguish aggregate artifacts by head SHA. Outgoing request headers, bodies, and complete command arguments are excluded. Credential values and fields are redacted before truncation. Response excerpts depend on gh's debug format and are not complete wire captures. Normal stdout, existing request arguments, permissions, review verdicts, submission deduplication, and retry behavior are preserved.
2.5 KiB
GitHub request diagnostics for code review
The runner, status aggregator and result-sync jobs load review_github_api.py from the immutable workflow commit before their first GitHub API request. The copy in RUNNER_TEMP survives the subsequent PR checkout and is shared by the submission helper and head watchdog.
Use python3 "$REVIEW_GH_HELPER" api ... in these workflow steps. Python helpers use run_gh with captured text output, check=True and their existing timeout; use decode_json when decoding its result. Request arguments, normal stdout, write deduplication, permission decisions and retry policy remain unchanged.
On a failure, the helper prints a short diagnostic and saves one JSON record under REVIEW_GITHUB_DIAGNOSTICS_DIR. Records include the command exit status or timeout, the last HTTP method/path/status, GitHub request ID, rate-limit and retry headers, gh version, and bounded response/stdout/stderr excerpts. A successful gh exit followed by a Python JSON decode error is also recorded. A transport failure without a response has status: null; it is not inferred to be an HTTP error.
The helper captures GH_DEBUG=api in memory because gh can discard a malformed error body and its HTTP status while parsing it. Outgoing headers, request bodies and argv are never archived. Response headers use an allowlist. Credential values and common credential fields are redacted before excerpts are truncated. The response body is gh's formatted debug excerpt, not exact wire bytes; gh may itself limit or omit it. Debug format changes may leave fields unavailable. Hard termination of the helper can prevent recording.
Each failure uses a separate file so later cleanup errors do not overwrite it. An always() artifact step runs after cleanup, including when an API failure is caught by an optional step. Artifacts are named github-api-errors---; reusable aggregation jobs include the head SHA to distinguish current-head and previous-head executions. Diagnostic upload/storage failures do not replace the original request failure.
The existing failed-code-review-* submission artifacts remain available. These additions do not change the failure notification's content or posting arguments, or its existing failure classification.
Local validation:
RUN_GH_HTTP_TESTS=1 python3 -m unittest discover -s .github/scripts -p 'test_*.py'
The opt-in HTTP test uses real gh only against a loopback server, with fake tokens and a temporary config; it never sends a request to GitHub. Linux-only process-supervision tests require a Linux host.