perf(message-stream): smooth live response rendering #1727
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: AI PR Review (Single) | |
| # Authentication defaults to Codex subscription auth for every allowed review. See | |
| # .github/action/ai-review.md for the supported subscription and API-key fallback configurations | |
| # and their security boundaries. | |
| # - Shared API secrets: OPENAI_API_KEY and CODEX_BASE_URL | |
| # - Review-specific secrets: CODEX_REVIEW_API_KEY and CODEX_REVIEW_BASE_URL | |
| # - Legacy fallback secrets: complete correctness-specific and then architecture-specific key/URL pairs | |
| # (base URLs may omit the trailing slash or include the full /v1/responses endpoint) | |
| # - Subscription bootstrap secret: CODEX_AUTH_JSON | |
| # Set CODEX_REVIEW_AUTH_MODE to subscription (default) or api-key. Automatic and manually | |
| # dispatched reviews honor this preference. Missing, invalid, or rejected subscription credentials | |
| # and subscription CLI setup failures fall back to the configured API-key credentials before | |
| # checkout and review execution. | |
| # Set ENABLE_CODEX_REVIEW=false to disable the automated reviewer. | |
| # Legacy CODEX_REVIEW_MODE=disabled keeps automatic reviews disabled while preserving dispatch. | |
| # CODEX_REVIEW_MODEL and CODEX_REVIEW_EFFORT select its model and effort. A selected legacy | |
| # credential pair uses model/effort from the same scope; generic credentials keep ordered fallbacks. | |
| # Set FORK_REVIEW_MODE to disabled, manual, or automatic (default: manual). Same-repository PRs | |
| # always run. Manual mode permits workflow_dispatch reviews of forks; automatic mode also reviews | |
| # fork PRs on opened, synchronize, and reopened events. Disabled mode blocks both fork paths. | |
| # Set CODEX_REVIEW_MAX_ROUNDS to the maximum successful comments per PR (default: 20, | |
| # 0 means unlimited). Rapid updates use concurrency cancellation so only the latest surviving run | |
| # publishes a review and consumes a round. | |
| # | |
| # The reviewer does not post comments directly. It writes one schema-validated verdict covering | |
| # correctness and architecture to a job output, and a separate trusted workflow publishes it. | |
| on: | |
| pull_request_target: | |
| branches: | |
| - main | |
| types: [opened, synchronize, reopened] | |
| workflow_dispatch: | |
| inputs: | |
| pull_request_number: | |
| description: Pull request number to review | |
| required: true | |
| concurrency: | |
| group: ai-pr-review-${{ github.event.inputs.pull_request_number || github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| review_target: | |
| name: Resolve AI review target | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: read | |
| outputs: | |
| number: ${{ steps.target.outputs.number }} | |
| head_ref: ${{ steps.target.outputs.head_ref }} | |
| head_sha: ${{ steps.target.outputs.head_sha }} | |
| base_sha: ${{ steps.target.outputs.base_sha }} | |
| review_sha: ${{ steps.target.outputs.review_sha }} | |
| state: ${{ steps.target.outputs.state }} | |
| title: ${{ steps.target.outputs.title }} | |
| is_fork: ${{ steps.target.outputs.is_fork }} | |
| fork_mode: ${{ steps.target.outputs.fork_mode }} | |
| review_allowed: ${{ steps.target.outputs.review_allowed }} | |
| review_enabled: ${{ steps.target.outputs.review_enabled }} | |
| auth_mode: ${{ steps.target.outputs.auth_mode }} | |
| credential_scope: ${{ steps.target.outputs.credential_scope }} | |
| steps: | |
| - name: Resolve pull request metadata | |
| id: target | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| DISPATCH_PR_NUMBER: ${{ github.event.inputs.pull_request_number }} | |
| EVENT_PR_NUMBER: ${{ github.event.pull_request.number }} | |
| FORK_REVIEW_MODE: ${{ vars.FORK_REVIEW_MODE || 'manual' }} | |
| CODEX_REVIEW_AUTH_MODE: ${{ vars.CODEX_REVIEW_AUTH_MODE || 'subscription' }} | |
| CODEX_REVIEW_MODE: ${{ vars.CODEX_REVIEW_MODE || 'correctness' }} | |
| ENABLE_CODEX_REVIEW: ${{ vars.ENABLE_CODEX_REVIEW || 'true' }} | |
| REVIEW_API_KEY_CONFIGURED: ${{ secrets.CODEX_REVIEW_API_KEY != '' }} | |
| REVIEW_BASE_URL_CONFIGURED: ${{ secrets.CODEX_REVIEW_BASE_URL != '' }} | |
| CORRECTNESS_API_KEY_CONFIGURED: ${{ secrets.CODEX_CORRECTNESS_API_KEY != '' }} | |
| CORRECTNESS_BASE_URL_CONFIGURED: ${{ secrets.CODEX_CORRECTNESS_BASE_URL != '' }} | |
| ARCHITECTURE_API_KEY_CONFIGURED: ${{ secrets.CODEX_ARCHITECTURE_API_KEY != '' }} | |
| ARCHITECTURE_BASE_URL_CONFIGURED: ${{ secrets.CODEX_ARCHITECTURE_BASE_URL != '' }} | |
| SHARED_API_KEY_CONFIGURED: ${{ secrets.OPENAI_API_KEY != '' }} | |
| SHARED_BASE_URL_CONFIGURED: ${{ secrets.CODEX_BASE_URL != '' }} | |
| REVIEW_EVENT: ${{ github.event_name }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "$FORK_REVIEW_MODE" in | |
| disabled|manual|automatic) ;; | |
| *) | |
| echo "FORK_REVIEW_MODE must be disabled, manual, or automatic." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| case "$ENABLE_CODEX_REVIEW" in | |
| true|false) ;; | |
| *) | |
| echo "ENABLE_CODEX_REVIEW must be true or false." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| case "$CODEX_REVIEW_AUTH_MODE" in | |
| api-key|subscription) ;; | |
| *) | |
| echo "CODEX_REVIEW_AUTH_MODE must be api-key or subscription." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| case "$CODEX_REVIEW_MODE" in | |
| both|correctness|architecture|disabled) ;; | |
| *) | |
| echo "CODEX_REVIEW_MODE must be both, correctness, architecture, or disabled." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| pr_number="${DISPATCH_PR_NUMBER:-$EVENT_PR_NUMBER}" | |
| if [[ -z "$pr_number" ]]; then | |
| echo "No pull request number available (workflow_dispatch input or pull_request event)." >&2 | |
| exit 1 | |
| fi | |
| gh pr view "$pr_number" --repo "$GH_REPO" \ | |
| --json number,headRefName,headRefOid,baseRefOid,title,isCrossRepository,state,mergeCommit \ | |
| > pr.json | |
| state="$(jq -r '.state' pr.json)" | |
| head_sha="$(jq -r '.headRefOid' pr.json)" | |
| review_sha="$head_sha" | |
| if [[ "$state" == "MERGED" ]]; then | |
| review_sha="$(jq -r '.mergeCommit.oid // empty' pr.json)" | |
| if [[ -z "$review_sha" ]]; then | |
| echo "Merged pull request has no merge commit OID." >&2 | |
| exit 1 | |
| fi | |
| fi | |
| is_fork="$(jq -r '.isCrossRepository' pr.json)" | |
| review_allowed=false | |
| if [[ "$is_fork" != "true" ]] || | |
| [[ "$REVIEW_EVENT" == "workflow_dispatch" && "$FORK_REVIEW_MODE" != "disabled" ]] || | |
| [[ "$REVIEW_EVENT" != "workflow_dispatch" && "$FORK_REVIEW_MODE" == "automatic" ]]; then | |
| review_allowed=true | |
| fi | |
| effective_auth_mode="$CODEX_REVIEW_AUTH_MODE" | |
| credential_scope=none | |
| if [[ "$REVIEW_API_KEY_CONFIGURED" == "true" && | |
| "$REVIEW_BASE_URL_CONFIGURED" == "true" ]]; then | |
| credential_scope=review | |
| elif [[ "$CORRECTNESS_API_KEY_CONFIGURED" == "true" && | |
| "$CORRECTNESS_BASE_URL_CONFIGURED" == "true" ]]; then | |
| credential_scope=correctness | |
| elif [[ "$ARCHITECTURE_API_KEY_CONFIGURED" == "true" && | |
| "$ARCHITECTURE_BASE_URL_CONFIGURED" == "true" ]]; then | |
| credential_scope=architecture | |
| elif [[ "$SHARED_API_KEY_CONFIGURED" == "true" && | |
| "$SHARED_BASE_URL_CONFIGURED" == "true" ]]; then | |
| credential_scope=shared | |
| fi | |
| review_enabled="$ENABLE_CODEX_REVIEW" | |
| if [[ "$REVIEW_EVENT" != "workflow_dispatch" && "$CODEX_REVIEW_MODE" == "disabled" ]]; then | |
| review_enabled=false | |
| fi | |
| { | |
| echo "number=$(jq -r '.number' pr.json)" | |
| echo "head_ref=$(jq -r '.headRefName' pr.json)" | |
| echo "head_sha=$head_sha" | |
| echo "base_sha=$(jq -r '.baseRefOid' pr.json)" | |
| echo "review_sha=$review_sha" | |
| echo "state=$state" | |
| echo "title=$(jq -r '.title' pr.json)" | |
| echo "is_fork=$is_fork" | |
| echo "fork_mode=$FORK_REVIEW_MODE" | |
| echo "review_allowed=$review_allowed" | |
| echo "review_enabled=$review_enabled" | |
| echo "auth_mode=$effective_auth_mode" | |
| echo "credential_scope=$credential_scope" | |
| } >> "$GITHUB_OUTPUT" | |
| codex_review_gate: | |
| name: Check Codex review limits | |
| needs: review_target | |
| if: >- | |
| ${{ needs.review_target.outputs.review_allowed == 'true' && | |
| needs.review_target.outputs.review_enabled == 'true' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: read | |
| pull-requests: read | |
| outputs: | |
| should_run: ${{ steps.gate.outputs.should_run }} | |
| steps: | |
| - name: Check Codex review counts | |
| id: gate | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ needs.review_target.outputs.number }} | |
| CODEX_REVIEW_MAX_ROUNDS: ${{ vars.CODEX_REVIEW_MAX_ROUNDS || '20' }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| max_reviews="$CODEX_REVIEW_MAX_ROUNDS" | |
| if [[ ! "$max_reviews" =~ ^[0-9]+$ ]]; then | |
| echo "CODEX_REVIEW_MAX_ROUNDS must be a non-negative integer." >&2 | |
| exit 1 | |
| fi | |
| gh api --paginate --slurp \ | |
| "repos/${GH_REPO}/issues/${PR_NUMBER}/comments?per_page=100" > comments.json | |
| review_count="$( | |
| jq -r ' | |
| [.[][] | |
| | select(.user.login == "github-actions[bot]") | |
| | select((.body // "") | contains("<!-- ai-review:codex -->"))] | |
| | length | |
| ' comments.json | |
| )" | |
| round=$((review_count + 1)) | |
| if (( max_reviews > 0 && review_count >= max_reviews )); then | |
| echo 'should_run=false' >> "$GITHUB_OUTPUT" | |
| echo "- Codex review skipped: ${review_count} successful reviews already published (limit ${max_reviews})." \ | |
| >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo 'should_run=true' >> "$GITHUB_OUTPUT" | |
| if (( max_reviews == 0 )); then | |
| echo "- Codex review round ${round} (unlimited)." >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "- Codex review round ${round} of ${max_reviews}." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| fi | |
| codex_review: | |
| name: Review | |
| needs: [review_target, codex_review_gate] | |
| if: ${{ needs.codex_review_gate.outputs.should_run == 'true' }} | |
| permissions: | |
| contents: read | |
| uses: ./.github/workflows/ai-codex-review.yml | |
| with: | |
| auth_mode: ${{ needs.review_target.outputs.auth_mode }} | |
| pull_request_number: ${{ needs.review_target.outputs.number }} | |
| head_ref: ${{ needs.review_target.outputs.head_ref }} | |
| head_sha: ${{ needs.review_target.outputs.head_sha }} | |
| base_sha: ${{ needs.review_target.outputs.base_sha }} | |
| review_sha: ${{ needs.review_target.outputs.review_sha }} | |
| review_state: ${{ needs.review_target.outputs.state }} | |
| review_event: ${{ github.event_name }} | |
| pull_request_title: ${{ needs.review_target.outputs.title }} | |
| is_fork: ${{ needs.review_target.outputs.is_fork == 'true' }} | |
| fork_mode: ${{ needs.review_target.outputs.fork_mode }} | |
| model: ${{ vars.CODEX_REVIEW_MODEL || (needs.review_target.outputs.credential_scope == 'correctness' && vars.CODEX_CORRECTNESS_MODEL) || (needs.review_target.outputs.credential_scope == 'architecture' && vars.CODEX_ARCHITECTURE_MODEL) || ((needs.review_target.outputs.credential_scope == 'review' || needs.review_target.outputs.credential_scope == 'shared' || needs.review_target.outputs.credential_scope == 'none') && (vars.CODEX_CORRECTNESS_MODEL || vars.CODEX_ARCHITECTURE_MODEL)) || 'gpt-5.6-sol' }} | |
| effort: ${{ vars.CODEX_REVIEW_EFFORT || (needs.review_target.outputs.credential_scope == 'correctness' && vars.CODEX_CORRECTNESS_EFFORT) || (needs.review_target.outputs.credential_scope == 'architecture' && vars.CODEX_ARCHITECTURE_EFFORT) || ((needs.review_target.outputs.credential_scope == 'review' || needs.review_target.outputs.credential_scope == 'shared' || needs.review_target.outputs.credential_scope == 'none') && (vars.CODEX_CORRECTNESS_EFFORT || vars.CODEX_ARCHITECTURE_EFFORT)) || 'high' }} | |
| secrets: | |
| CODEX_AUTH_JSON: ${{ needs.review_target.outputs.auth_mode == 'subscription' && secrets.CODEX_AUTH_JSON || '' }} | |
| OPENAI_API_KEY: ${{ (needs.review_target.outputs.credential_scope == 'review' && secrets.CODEX_REVIEW_API_KEY) || (needs.review_target.outputs.credential_scope == 'correctness' && secrets.CODEX_CORRECTNESS_API_KEY) || (needs.review_target.outputs.credential_scope == 'architecture' && secrets.CODEX_ARCHITECTURE_API_KEY) || (needs.review_target.outputs.credential_scope == 'shared' && secrets.OPENAI_API_KEY) || '' }} | |
| CODEX_BASE_URL: ${{ (needs.review_target.outputs.credential_scope == 'review' && secrets.CODEX_REVIEW_BASE_URL) || (needs.review_target.outputs.credential_scope == 'correctness' && secrets.CODEX_CORRECTNESS_BASE_URL) || (needs.review_target.outputs.credential_scope == 'architecture' && secrets.CODEX_ARCHITECTURE_BASE_URL) || (needs.review_target.outputs.credential_scope == 'shared' && secrets.CODEX_BASE_URL) || '' }} | |
| post_codex_feedback: | |
| name: Post Codex feedback | |
| needs: codex_review | |
| if: >- | |
| ${{ needs.codex_review.result == 'success' && | |
| needs.codex_review.outputs.review_body != '' }} | |
| uses: ./.github/workflows/ai-post-review.yml | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| with: | |
| scope: review | |
| marker: '<!-- ai-review:codex -->' | |
| header: '## Codex Review' | |
| review_body: ${{ needs.codex_review.outputs.review_body }} | |
| pull_request_number: ${{ needs.codex_review.outputs.pr_number }} | |
| head_sha: ${{ needs.codex_review.outputs.head_sha }} | |
| max_rounds: ${{ vars.CODEX_REVIEW_MAX_ROUNDS || '20' }} | |
| apply_review_outcome: | |
| name: Apply review outcome label | |
| if: ${{ always() && needs.review_target.result == 'success' }} | |
| needs: | |
| - review_target | |
| - codex_review | |
| - post_codex_feedback | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Apply ready-to-merge from published review outputs | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| env: | |
| PR_NUMBER: ${{ needs.review_target.outputs.number }} | |
| REVIEW_HEAD_SHA: ${{ needs.review_target.outputs.head_sha }} | |
| REVIEW_REQUIRED: ${{ needs.review_target.outputs.review_enabled }} | |
| REVIEW_RESULT: ${{ needs.codex_review.result }} | |
| REVIEW_POST_RESULT: ${{ needs.post_codex_feedback.result }} | |
| REVIEW_POSTED: ${{ needs.post_codex_feedback.outputs.posted }} | |
| REVIEW_BODY: ${{ needs.codex_review.outputs.review_body }} | |
| with: | |
| github-token: ${{ github.token }} | |
| retries: 3 | |
| script: | | |
| const issueNumber = Number(process.env.PR_NUMBER); | |
| const reviewedHeadSha = process.env.REVIEW_HEAD_SHA; | |
| const labelName = 'ready-to-merge'; | |
| const reviewers = [ | |
| { | |
| name: 'Codex review', | |
| header: '## Codex Review', | |
| required: process.env.REVIEW_REQUIRED === 'true', | |
| result: process.env.REVIEW_RESULT, | |
| postResult: process.env.REVIEW_POST_RESULT, | |
| posted: process.env.REVIEW_POSTED, | |
| body: process.env.REVIEW_BODY, | |
| }, | |
| ]; | |
| const removeReadyLabel = async () => { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| name: labelName, | |
| }); | |
| } catch (error) { | |
| if (error.status !== 404) throw error; | |
| } | |
| }; | |
| const { data: pullRequest } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: issueNumber, | |
| }); | |
| if (pullRequest.state !== 'open') { | |
| core.notice('Removing ready-to-merge because the pull request is not open.'); | |
| await removeReadyLabel(); | |
| return; | |
| } | |
| if (!reviewedHeadSha || pullRequest.head.sha !== reviewedHeadSha) { | |
| core.notice('Skipping labels because a newer pull request commit exists.'); | |
| return; | |
| } | |
| const active = reviewers.filter(({ required }) => required); | |
| if (active.length === 0) { | |
| core.warning('No AI reviewer ran; removing ready-to-merge.'); | |
| await removeReadyLabel(); | |
| return; | |
| } | |
| const unpublished = active.filter( | |
| ({ result, postResult, posted }) => | |
| result !== 'success' || postResult !== 'success' || posted !== 'true', | |
| ); | |
| if (unpublished.length > 0) { | |
| const names = unpublished.map(({ name }) => name).join(', '); | |
| core.warning(`Review output was not published for: ${names}; removing ${labelName}.`); | |
| await removeReadyLabel(); | |
| return; | |
| } | |
| const verdicts = []; | |
| for (const { name, header, body } of active) { | |
| const matches = [...(body ?? '').matchAll( | |
| /^\*\*Verdict:\s*(mergeable|needs changes)\*\*$/gm, | |
| )]; | |
| if (!body?.startsWith(header) || matches.length !== 1) { | |
| core.warning(`No unambiguous verdict output for "${name}"; removing ${labelName}.`); | |
| await removeReadyLabel(); | |
| return; | |
| } | |
| verdicts.push(matches[0][1]); | |
| } | |
| if (verdicts.includes('needs changes')) { | |
| core.notice('At least one reviewer requested changes; removing ready-to-merge.'); | |
| await removeReadyLabel(); | |
| return; | |
| } | |
| try { | |
| await github.rest.issues.createLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: labelName, | |
| color: '1f883d', | |
| description: 'The completed AI reviewer found this pull request mergeable.', | |
| }); | |
| } catch (error) { | |
| if (error.status !== 422) throw error; | |
| } | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| labels: [labelName], | |
| }); |