fix(release): preserve runtime environment boundary #574
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Claude Review | |
| # Genuine, advisory AI code review on every PR. Posts findings as ONE | |
| # top-level PR comment. NOT a required status check — it never blocks a | |
| # merge. Top-level only (no inline review comments) BY DESIGN: inline | |
| # comments create review threads, and the "Require conversation resolution | |
| # before merging" branch-protection rule blocks auto-merge on any unresolved | |
| # thread regardless of whether the check that posted it is required. The | |
| # previous auto-resolve step could not fix this — the workflow GITHUB_TOKEN | |
| # gets FORBIDDEN on the resolveReviewThread mutation — so every commented PR | |
| # deadlocked until a maintainer hand-resolved threads (see PR #289). | |
| # Uses `pull_request` (not pull_request_target) so ANTHROPIC_API_KEY is | |
| # never exposed to fork PRs. | |
| # | |
| # FAILS LOUDLY BY DESIGN. Between 2026-07-08 05:42 and 14:49 UTC every run | |
| # started failing at the first API call (~300ms, 1 turn, $0.00 cost) and the | |
| # job still went green for ~4 weeks: claude-code-action derives its conclusion | |
| # from `resultMessage.subtype === "success"` and never inspects `is_error`, so | |
| # an `{"subtype":"success","is_error":true}` result looks like a clean run and | |
| # the tracking comment is left on its "I'll analyze this and get back to you." | |
| # placeholder. The preflight and result-guard steps below close that hole. The | |
| # job is still NOT a required check, so a red X here never blocks a merge. | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| permissions: | |
| contents: read | |
| jobs: | |
| review: | |
| # Dependabot-triggered runs cannot read repo secrets, so the Claude review | |
| # action always fails with a red X on dependabot PRs. Skip them (same | |
| # convention as scripts/check-changesets.mjs). | |
| if: github.actor != 'dependabot[bot]' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| # One ~$0.00003 call that names the failure instead of leaving it buried | |
| # in a redacted agent log: 401 = key revoked/invalid, 400 with | |
| # `credit balance` = org out of credits, 404 = REVIEW_MODEL retired. | |
| # Without this the only symptom is a 300ms is_error result with no | |
| # message, because the action redacts agent output unless | |
| # `show_full_output: true` (which would dump the whole review transcript). | |
| - name: Preflight — Anthropic credentials and model | |
| id: preflight | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| REVIEW_MODEL: claude-sonnet-4-6 | |
| run: | | |
| set -uo pipefail | |
| if [ -z "${ANTHROPIC_API_KEY}" ]; then | |
| echo "::warning::ANTHROPIC_API_KEY is unavailable (fork PR — secrets are not exposed to fork \`pull_request\` runs). Skipping review." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| # Keep the key out of argv (and therefore out of the process list) by | |
| # passing it through a 0600 curl config file rather than -H. | |
| rc="$(mktemp)"; body="$(mktemp)" | |
| trap 'rm -f "$rc" "$body"' EXIT | |
| printf 'header = "x-api-key: %s"\n' "${ANTHROPIC_API_KEY}" > "$rc" | |
| code="$(curl -sS -o "$body" -w '%{http_code}' \ | |
| --config "$rc" \ | |
| -H 'anthropic-version: 2023-06-01' \ | |
| -H 'content-type: application/json' \ | |
| -d "{\"model\":\"${REVIEW_MODEL}\",\"max_tokens\":1,\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" \ | |
| https://api.anthropic.com/v1/messages)" || { | |
| echo "::warning::Preflight request could not be sent; continuing and letting the review step report." | |
| exit 0 | |
| } | |
| case "$code" in | |
| 200) | |
| echo "Anthropic API reachable; key valid; model ${REVIEW_MODEL} available." | |
| ;; | |
| 429|5??) | |
| echo "::warning::Preflight got HTTP ${code} (rate limit / transient). Key looks valid; continuing." | |
| ;; | |
| *) | |
| echo "::error::Anthropic preflight failed with HTTP ${code} for model ${REVIEW_MODEL} — the review agent cannot run." | |
| jq -r '.error | "\(.type): \(.message)"' < "$body" 2>/dev/null || head -c 500 "$body" | |
| echo | |
| echo "401 => rotate ANTHROPIC_API_KEY. 400 mentioning credit balance => top up the Anthropic org. 404 => REVIEW_MODEL is no longer available to this org." | |
| exit 1 | |
| ;; | |
| esac | |
| - name: Claude review | |
| id: claude | |
| if: steps.preflight.outputs.skip != 'true' | |
| uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1 | |
| with: | |
| # Pass GITHUB_TOKEN explicitly (maps to OVERRIDE_GITHUB_TOKEN) so the | |
| # action skips OIDC token exchange — that path needs `id-token: write` | |
| # plus the Claude GitHub App, which this repo doesn't use. Posts as | |
| # github-actions[bot] via the job's pull-requests: write scope. | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| track_progress: true | |
| prompt: | | |
| REPO: ${{ github.repository }} | |
| PR NUMBER: ${{ github.event.pull_request.number }} | |
| Review this pull request and post your findings as ONE top-level | |
| GitHub PR comment via `gh pr comment`. Do NOT post inline review | |
| comments (they create review threads that block auto-merge under | |
| this repo's branch protection). | |
| Read the diff with `gh pr diff` and the description with `gh pr view`. | |
| Focus on: | |
| - Correctness bugs | |
| - Security issues (injection, secrets, unsafe input handling) | |
| - TypeScript type-safety problems | |
| - Missing or weak test coverage for the change | |
| In the comment, reference specific locations as `path/to/file.ts:123` | |
| code-formatted paths with a short quoted snippet, so findings are easy | |
| to locate without inline threads. Order findings most-severe first. | |
| Be brief; skip nitpicks and style unless they affect correctness. If | |
| the PR looks good, say so briefly. If a prior review comment of yours | |
| exists on this PR, post a fresh comment covering only what changed | |
| since (do not repeat findings that were addressed). | |
| claude_args: | | |
| --model claude-sonnet-4-6 | |
| --max-turns 40 | |
| --allowedTools "Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" | |
| # The action reports success whenever the result subtype is "success", | |
| # even when that same result carries is_error: true. Re-check the raw | |
| # execution log (unredacted, unlike the console output) and fail on it. | |
| - name: Verify the review actually ran | |
| if: always() && steps.claude.conclusion != 'skipped' | |
| env: | |
| EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }} | |
| run: | | |
| set -uo pipefail | |
| if [ -z "${EXECUTION_FILE}" ] || [ ! -f "${EXECUTION_FILE}" ]; then | |
| echo "::error::No Claude execution log was produced — the review agent never reported a result." | |
| exit 1 | |
| fi | |
| result="$(jq -c '[.[] | select(.type == "result")] | last // empty' "${EXECUTION_FILE}")" | |
| if [ -z "${result}" ]; then | |
| echo "::error::Claude execution log contains no result message — the agent did not finish." | |
| exit 1 | |
| fi | |
| is_error="$(jq -r '.is_error // false' <<<"${result}")" | |
| subtype="$(jq -r '.subtype // "unknown"' <<<"${result}")" | |
| if [ "${is_error}" = "true" ] || [ "${subtype}" != "success" ]; then | |
| echo "::error::Claude review failed (subtype=${subtype}, is_error=${is_error}). No review was posted." | |
| echo "--- agent error detail (truncated) ---" | |
| jq -r '.result // .error // "no detail in result message"' <<<"${result}" | head -c 2000 | |
| echo | |
| exit 1 | |
| fi | |
| turns="$(jq -r '.num_turns // 0' <<<"${result}")" | |
| cost="$(jq -r '.total_cost_usd // 0' <<<"${result}")" | |
| echo "Review completed: ${turns} turn(s), \$${cost}." |