From ec80aca69b0e649c9669ffdcfad36e02babd3d98 Mon Sep 17 00:00:00 2001 From: eottabom Date: Sat, 8 Aug 2026 08:27:59 +0900 Subject: [PATCH 1/3] =?UTF-8?q?ci:=20PR=20=EC=83=9D=EC=84=B1=20=EC=8B=9C?= =?UTF-8?q?=20codex/claude=20=EC=9E=90=EB=8F=99=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C?= =?UTF-8?q?=EC=9A=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pull_request opened 이벤트에서 claude-review, codex-review 잡을 병렬로 실행해 pulls/reviews API로 정식 GitHub 리뷰를 남긴다. undefined-lab의 슬래시 커맨드 리뷰 잡 패턴을 재사용하되 수동 트리거 대신 PR 생성 시 자동으로 동작하도록 구성했다. Closes #457 --- .github/scripts/claude-review-run.sh | 48 +++++++++++++ .github/scripts/codex-review-run.sh | 50 +++++++++++++ .github/workflows/pr-auto-review.yml | 103 +++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100755 .github/scripts/claude-review-run.sh create mode 100755 .github/scripts/codex-review-run.sh create mode 100644 .github/workflows/pr-auto-review.yml diff --git a/.github/scripts/claude-review-run.sh b/.github/scripts/claude-review-run.sh new file mode 100755 index 0000000..6eeceb0 --- /dev/null +++ b/.github/scripts/claude-review-run.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +export PATH="/Users/yukeun/.local/bin:$PATH" + +REVIEW_FILE="/tmp/claude-review-${PR_NUMBER}.json" + +PROMPT_FILE=$(mktemp /tmp/claude-review-prompt.XXXXXX.txt) +trap 'rm -f "$PROMPT_FILE"' EXIT + +cat > "$PROMPT_FILE" < |\n| 🔁 Regression Risk | <🔴 매우 높음 / 🟡 보통 / 🟢 낮음 + 한 줄 이유> |\n| ⚠️ Risky Changes | <있음: 내용 요약 / 없음> |\n| 🧪 Missing Tests | <있음: 내용 요약 / 없음> |\n\n### 📝 상세 리뷰\n\n<상세 리뷰 내용을 마크다운으로 작성. 파일명, 라인, 근거를 구체적으로>", + "event": "COMMENT", + "comments": [ + { + "path": "relative/path/to/file.java", + "line": , + "side": "RIGHT", + "body": "" + } + ] +} + +7. Post the review: + gh api /repos/eottabom/sandbox-load-test/pulls/${PR_NUMBER}/reviews --method POST --input ${REVIEW_FILE} + +Rules: +- Only add inline comments on lines that exist in the diff (added or changed lines on the RIGHT side). +- The 'line' must be the actual line number in the current file, not the diff position. +- If there are no specific inline findings, use an empty array for 'comments'. +- If there are no findings at all, still post a summary saying so. +- Write everything in Korean. +PROMPT_EOF + +claude --dangerously-skip-permissions --verbose --output-format stream-json < "$PROMPT_FILE" diff --git a/.github/scripts/codex-review-run.sh b/.github/scripts/codex-review-run.sh new file mode 100755 index 0000000..7e5ad94 --- /dev/null +++ b/.github/scripts/codex-review-run.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +export PATH="/Users/yukeun/.local/bin:$PATH" + +REVIEW_FILE="/tmp/codex-review-${PR_NUMBER}.json" + +PROMPT_FILE=$(mktemp /tmp/codex-review-prompt.XXXXXX.txt) +trap 'rm -f "$PROMPT_FILE"' EXIT + +cat > "$PROMPT_FILE" < |\n| 🔁 Regression Risk | <🔴 매우 높음 / 🟡 보통 / 🟢 낮음 + 한 줄 이유> |\n| ⚠️ Risky Changes | <있음: 내용 요약 / 없음> |\n| 🧪 Missing Tests | <있음: 내용 요약 / 없음> |\n\n### 📝 상세 리뷰\n\n<상세 리뷰 내용을 마크다운으로 작성. 파일명, 라인, 근거를 구체적으로>", + "event": "COMMENT", + "comments": [ + { + "path": "relative/path/to/file.java", + "line": , + "side": "RIGHT", + "body": "" + } + ] +} + +7. Post the review: + gh api /repos/eottabom/sandbox-load-test/pulls/${PR_NUMBER}/reviews --method POST --input ${REVIEW_FILE} + +Rules: +- Only add inline comments on lines that exist in the diff (added or changed lines on the RIGHT side). +- The 'line' must be the actual line number in the current file, not the diff position. +- If there are no specific inline findings, use an empty array for 'comments'. +- If there are no findings at all, still post a summary saying so. +- Write everything in Korean. +PROMPT_EOF + +codex exec \ + -C "/Users/yukeun/workspace/sandbox-load-test" \ + --dangerously-bypass-approvals-and-sandbox \ + - < "$PROMPT_FILE" diff --git a/.github/workflows/pr-auto-review.yml b/.github/workflows/pr-auto-review.yml new file mode 100644 index 0000000..b21edb9 --- /dev/null +++ b/.github/workflows/pr-auto-review.yml @@ -0,0 +1,103 @@ +name: PR Auto Review + +on: + pull_request: + types: [opened, ready_for_review] + +permissions: + contents: read + pull-requests: write + +jobs: + claude-review: + if: github.event.pull_request.draft == false + timeout-minutes: 30 + concurrency: + group: claude-review-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + runs-on: [self-hosted, macOS, X64] + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + + defaults: + run: + working-directory: /Users/yukeun/workspace/sandbox-load-test + + steps: + - name: Checkout PR branch + run: | + git remote set-url origin https://x-access-token:${{ secrets.BOT_TOKEN }}@github.com/eottabom/sandbox-load-test.git + git fetch origin + git checkout ${{ github.event.pull_request.head.ref }} 2>/dev/null || git checkout -b ${{ github.event.pull_request.head.ref }} origin/${{ github.event.pull_request.head.ref }} + git reset --hard origin/${{ github.event.pull_request.head.ref }} + + - name: Check Claude login status + id: claude_auth + env: + HOME: /Users/yukeun + GH_TOKEN: ${{ secrets.BOT_TOKEN }} + run: | + export PATH="/Users/yukeun/.local/bin:$PATH" + if claude auth status 2>/dev/null | jq -e '.loggedIn == true' >/dev/null; then + echo "logged_in=true" >> "$GITHUB_OUTPUT" + else + echo "logged_in=false" >> "$GITHUB_OUTPUT" + gh pr comment "$PR_NUMBER" --repo eottabom/sandbox-load-test --body "⚠️ **eottabom-bot** self-hosted 러너의 \`claude\` CLI 로그인 세션이 만료되어 자동 리뷰를 실행할 수 없습니다. 러너 머신에서 \`claude /login\`으로 재로그인해 주세요." + fi + + - name: Run Claude review + if: steps.claude_auth.outputs.logged_in == 'true' + env: + HOME: /Users/yukeun + CI: "" + GH_TOKEN: ${{ secrets.BOT_TOKEN }} + run: | + unset CI + unset GITHUB_ACTIONS + unset GITHUB_TOKEN + bash /Users/yukeun/workspace/sandbox-load-test/.github/scripts/claude-review-run.sh 2>&1 | \ + jq --unbuffered -r ' + if .type == "assistant" then + (.message.content // [])[] | + if .type == "text" then .text + elif .type == "tool_use" then "[tool:\(.name)] \(.input | tostring | .[0:200])" + else empty end + elif .type == "tool_result" then + " → \((.content // [])[] | select(.type == "text") | .text | .[0:300])" + elif .type == "result" then + "\n=== \(.subtype) ===" + else empty end + ' 2>/dev/null || true + + codex-review: + if: github.event.pull_request.draft == false + timeout-minutes: 30 + concurrency: + group: codex-review-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + runs-on: [self-hosted, macOS, X64] + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + + defaults: + run: + working-directory: /Users/yukeun/workspace/sandbox-load-test + + steps: + - name: Checkout PR branch + run: | + git remote set-url origin https://x-access-token:${{ secrets.BOT_TOKEN }}@github.com/eottabom/sandbox-load-test.git + git fetch origin + git checkout ${{ github.event.pull_request.head.ref }} 2>/dev/null || git checkout -b ${{ github.event.pull_request.head.ref }} origin/${{ github.event.pull_request.head.ref }} + git reset --hard origin/${{ github.event.pull_request.head.ref }} + + - name: Run Codex review + env: + HOME: /Users/yukeun + CI: "" + GH_TOKEN: ${{ secrets.BOT_TOKEN }} + run: | + unset CI + bash /Users/yukeun/workspace/sandbox-load-test/.github/scripts/codex-review-run.sh From 3c48ec13563430fe8f02e15f656ad6c7442dd0bf Mon Sep 17 00:00:00 2001 From: eottabom Date: Sat, 8 Aug 2026 08:45:36 +0900 Subject: [PATCH 2/3] =?UTF-8?q?ci:=20=EB=A6=AC=EB=B7=B0=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=ED=95=98=EB=93=9C?= =?UTF-8?q?=EC=BD=94=EB=94=A9=20=EA=B2=BD=EB=A1=9C=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A9=98=EC=85=98=20=EC=9E=AC=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 러너 작업 디렉터리/HOME을 vars.RUNNER_WORKDIR, vars.RUNNER_HOME으로 분리해 public 저장소 워크플로우 파일에 로컬 사용자 경로가 노출되지 않게 한다. - 별도 봇 계정 PAT 없이 기본 GITHUB_TOKEN으로 리뷰 코멘트를 남기도록 단순화. - PR 코멘트에 @eottabom-review-bot을 멘션하면 claude/codex 리뷰를 다시 실행하는 pr-mention-review.yml 추가. --- .github/scripts/claude-review-run.sh | 2 +- .github/scripts/codex-review-run.sh | 5 +- .github/workflows/pr-auto-review.yml | 31 ++--- .github/workflows/pr-mention-review.yml | 162 ++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/pr-mention-review.yml diff --git a/.github/scripts/claude-review-run.sh b/.github/scripts/claude-review-run.sh index 6eeceb0..3465152 100755 --- a/.github/scripts/claude-review-run.sh +++ b/.github/scripts/claude-review-run.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -export PATH="/Users/yukeun/.local/bin:$PATH" +export PATH="$HOME/.local/bin:$PATH" REVIEW_FILE="/tmp/claude-review-${PR_NUMBER}.json" diff --git a/.github/scripts/codex-review-run.sh b/.github/scripts/codex-review-run.sh index 7e5ad94..7d51392 100755 --- a/.github/scripts/codex-review-run.sh +++ b/.github/scripts/codex-review-run.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash set -euo pipefail -export PATH="/Users/yukeun/.local/bin:$PATH" +export PATH="$HOME/.local/bin:$PATH" +: "${RUNNER_WORKDIR:?RUNNER_WORKDIR env var must be set}" REVIEW_FILE="/tmp/codex-review-${PR_NUMBER}.json" @@ -45,6 +46,6 @@ Rules: PROMPT_EOF codex exec \ - -C "/Users/yukeun/workspace/sandbox-load-test" \ + -C "$RUNNER_WORKDIR" \ --dangerously-bypass-approvals-and-sandbox \ - < "$PROMPT_FILE" diff --git a/.github/workflows/pr-auto-review.yml b/.github/workflows/pr-auto-review.yml index b21edb9..ab6df31 100644 --- a/.github/workflows/pr-auto-review.yml +++ b/.github/workflows/pr-auto-review.yml @@ -1,4 +1,4 @@ -name: PR Auto Review +name: 🤖 PR Auto Review on: pull_request: @@ -22,12 +22,12 @@ jobs: defaults: run: - working-directory: /Users/yukeun/workspace/sandbox-load-test + working-directory: ${{ vars.RUNNER_WORKDIR }} steps: - name: Checkout PR branch run: | - git remote set-url origin https://x-access-token:${{ secrets.BOT_TOKEN }}@github.com/eottabom/sandbox-load-test.git + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git git fetch origin git checkout ${{ github.event.pull_request.head.ref }} 2>/dev/null || git checkout -b ${{ github.event.pull_request.head.ref }} origin/${{ github.event.pull_request.head.ref }} git reset --hard origin/${{ github.event.pull_request.head.ref }} @@ -35,28 +35,28 @@ jobs: - name: Check Claude login status id: claude_auth env: - HOME: /Users/yukeun - GH_TOKEN: ${{ secrets.BOT_TOKEN }} + HOME: ${{ vars.RUNNER_HOME }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - export PATH="/Users/yukeun/.local/bin:$PATH" + export PATH="$HOME/.local/bin:$PATH" if claude auth status 2>/dev/null | jq -e '.loggedIn == true' >/dev/null; then echo "logged_in=true" >> "$GITHUB_OUTPUT" else echo "logged_in=false" >> "$GITHUB_OUTPUT" - gh pr comment "$PR_NUMBER" --repo eottabom/sandbox-load-test --body "⚠️ **eottabom-bot** self-hosted 러너의 \`claude\` CLI 로그인 세션이 만료되어 자동 리뷰를 실행할 수 없습니다. 러너 머신에서 \`claude /login\`으로 재로그인해 주세요." + gh pr comment "$PR_NUMBER" --repo eottabom/sandbox-load-test --body "⚠️ **eottabom-review-bot** self-hosted 러너의 \`claude\` CLI 로그인 세션이 만료되어 자동 리뷰를 실행할 수 없습니다. 러너 머신에서 \`claude /login\`으로 재로그인해 주세요." fi - name: Run Claude review if: steps.claude_auth.outputs.logged_in == 'true' env: - HOME: /Users/yukeun + HOME: ${{ vars.RUNNER_HOME }} CI: "" - GH_TOKEN: ${{ secrets.BOT_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | unset CI unset GITHUB_ACTIONS unset GITHUB_TOKEN - bash /Users/yukeun/workspace/sandbox-load-test/.github/scripts/claude-review-run.sh 2>&1 | \ + bash "${{ vars.RUNNER_WORKDIR }}/.github/scripts/claude-review-run.sh" 2>&1 | \ jq --unbuffered -r ' if .type == "assistant" then (.message.content // [])[] | @@ -83,21 +83,22 @@ jobs: defaults: run: - working-directory: /Users/yukeun/workspace/sandbox-load-test + working-directory: ${{ vars.RUNNER_WORKDIR }} steps: - name: Checkout PR branch run: | - git remote set-url origin https://x-access-token:${{ secrets.BOT_TOKEN }}@github.com/eottabom/sandbox-load-test.git + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git git fetch origin git checkout ${{ github.event.pull_request.head.ref }} 2>/dev/null || git checkout -b ${{ github.event.pull_request.head.ref }} origin/${{ github.event.pull_request.head.ref }} git reset --hard origin/${{ github.event.pull_request.head.ref }} - name: Run Codex review env: - HOME: /Users/yukeun + HOME: ${{ vars.RUNNER_HOME }} CI: "" - GH_TOKEN: ${{ secrets.BOT_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUNNER_WORKDIR: ${{ vars.RUNNER_WORKDIR }} run: | unset CI - bash /Users/yukeun/workspace/sandbox-load-test/.github/scripts/codex-review-run.sh + bash "${{ vars.RUNNER_WORKDIR }}/.github/scripts/codex-review-run.sh" diff --git a/.github/workflows/pr-mention-review.yml b/.github/workflows/pr-mention-review.yml new file mode 100644 index 0000000..479b529 --- /dev/null +++ b/.github/workflows/pr-mention-review.yml @@ -0,0 +1,162 @@ +name: 💬 PR Mention Re-review + +on: + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: write + +jobs: + claude-review: + if: | + contains(github.event.comment.body, '@eottabom-review-bot') && + github.event.issue.pull_request != null && + github.event.comment.user.type != 'Bot' + timeout-minutes: 30 + concurrency: + group: claude-review-pr-${{ github.event.issue.number }} + cancel-in-progress: true + runs-on: [self-hosted, macOS, X64] + env: + PR_NUMBER: ${{ github.event.issue.number }} + + defaults: + run: + working-directory: ${{ vars.RUNNER_WORKDIR }} + + steps: + - name: React with eyes emoji + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes' + }); + + - name: Get PR info + id: pr_info + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.issue.number + }); + core.setOutput('branch', pr.head.ref); + core.setOutput('base_ref', pr.base.ref); + + - name: Checkout PR branch + run: | + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git + git fetch origin + git checkout ${{ steps.pr_info.outputs.branch }} 2>/dev/null || git checkout -b ${{ steps.pr_info.outputs.branch }} origin/${{ steps.pr_info.outputs.branch }} + git reset --hard origin/${{ steps.pr_info.outputs.branch }} + + - name: Check Claude login status + id: claude_auth + env: + HOME: ${{ vars.RUNNER_HOME }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + export PATH="$HOME/.local/bin:$PATH" + if claude auth status 2>/dev/null | jq -e '.loggedIn == true' >/dev/null; then + echo "logged_in=true" >> "$GITHUB_OUTPUT" + else + echo "logged_in=false" >> "$GITHUB_OUTPUT" + gh pr comment "$PR_NUMBER" --repo eottabom/sandbox-load-test --body "⚠️ **eottabom-review-bot** self-hosted 러너의 \`claude\` CLI 로그인 세션이 만료되어 재리뷰를 실행할 수 없습니다. 러너 머신에서 \`claude /login\`으로 재로그인해 주세요." + fi + + - name: Run Claude review + if: steps.claude_auth.outputs.logged_in == 'true' + env: + HOME: ${{ vars.RUNNER_HOME }} + CI: "" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BASE_REF: ${{ steps.pr_info.outputs.base_ref }} + run: | + unset CI + unset GITHUB_ACTIONS + unset GITHUB_TOKEN + bash "${{ vars.RUNNER_WORKDIR }}/.github/scripts/claude-review-run.sh" 2>&1 | \ + jq --unbuffered -r ' + if .type == "assistant" then + (.message.content // [])[] | + if .type == "text" then .text + elif .type == "tool_use" then "[tool:\(.name)] \(.input | tostring | .[0:200])" + else empty end + elif .type == "tool_result" then + " → \((.content // [])[] | select(.type == "text") | .text | .[0:300])" + elif .type == "result" then + "\n=== \(.subtype) ===" + else empty end + ' 2>/dev/null || true + + codex-review: + if: | + contains(github.event.comment.body, '@eottabom-review-bot') && + github.event.issue.pull_request != null && + github.event.comment.user.type != 'Bot' + timeout-minutes: 30 + concurrency: + group: codex-review-pr-${{ github.event.issue.number }} + cancel-in-progress: true + runs-on: [self-hosted, macOS, X64] + env: + PR_NUMBER: ${{ github.event.issue.number }} + + defaults: + run: + working-directory: ${{ vars.RUNNER_WORKDIR }} + + steps: + - name: React with eyes emoji + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes' + }); + + - name: Get PR info + id: pr_info + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.issue.number + }); + core.setOutput('branch', pr.head.ref); + core.setOutput('base_ref', pr.base.ref); + + - name: Checkout PR branch + run: | + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git + git fetch origin + git checkout ${{ steps.pr_info.outputs.branch }} 2>/dev/null || git checkout -b ${{ steps.pr_info.outputs.branch }} origin/${{ steps.pr_info.outputs.branch }} + git reset --hard origin/${{ steps.pr_info.outputs.branch }} + + - name: Run Codex review + env: + HOME: ${{ vars.RUNNER_HOME }} + CI: "" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BASE_REF: ${{ steps.pr_info.outputs.base_ref }} + RUNNER_WORKDIR: ${{ vars.RUNNER_WORKDIR }} + run: | + unset CI + bash "${{ vars.RUNNER_WORKDIR }}/.github/scripts/codex-review-run.sh" From e25773371dd6d65a2ef07bf4844f63138dccc765 Mon Sep 17 00:00:00 2001 From: eottabom Date: Sat, 8 Aug 2026 09:14:03 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B7=B0=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=85=B8=20=EC=9D=B8?= =?UTF-8?q?=EC=A0=9D=EC=85=98=20=EB=B0=8F=20=EC=8B=A4=ED=8C=A8=20=EC=9D=80?= =?UTF-8?q?=ED=8F=90=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - git checkout에 PR head.ref를 직접 셸 확장하던 부분을 제거하고 refs/pull//head fetch + head_sha 검증으로 대체해 브랜치명을 통한 템플릿/셸 인젝션 가능성을 없앤다. - origin remote에 남아있던 토큰을 fetch 직후 제거해 영구 작업 디렉터리에 자격증명이 남지 않게 한다. - claude-review 잡의 파이프라인 `|| true`가 claude 실행 자체의 실패까지 삼키던 문제를 set +e/PIPESTATUS로 교체해 claude 스크립트의 실제 종료 코드만 잡의 성패로 반영한다. - codex-review에도 claude-review와 동일한 CLI 로그인 상태 사전 점검과 만료 알림 코멘트를 추가한다. - PR 작성자/코멘트 작성자가 OWNER/MEMBER/COLLABORATOR가 아니면 자동/멘션 리뷰가 실행되지 않도록 제한해, 외부 PR이 self-hosted 러너에서 신뢰되지 않은 스크립트를 실행하는 범위를 줄인다. --- .github/workflows/pr-auto-review.yml | 58 ++++++++++++++++++------ .github/workflows/pr-mention-review.yml | 59 ++++++++++++++++++------- 2 files changed, 89 insertions(+), 28 deletions(-) diff --git a/.github/workflows/pr-auto-review.yml b/.github/workflows/pr-auto-review.yml index ab6df31..b444c2c 100644 --- a/.github/workflows/pr-auto-review.yml +++ b/.github/workflows/pr-auto-review.yml @@ -10,7 +10,9 @@ permissions: jobs: claude-review: - if: github.event.pull_request.draft == false + if: | + github.event.pull_request.draft == false && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association) timeout-minutes: 30 concurrency: group: claude-review-pr-${{ github.event.pull_request.number }} @@ -25,12 +27,16 @@ jobs: working-directory: ${{ vars.RUNNER_WORKDIR }} steps: - - name: Checkout PR branch + - name: Checkout PR head + env: + HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git - git fetch origin - git checkout ${{ github.event.pull_request.head.ref }} 2>/dev/null || git checkout -b ${{ github.event.pull_request.head.ref }} origin/${{ github.event.pull_request.head.ref }} - git reset --hard origin/${{ github.event.pull_request.head.ref }} + git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git" + git fetch origin "refs/pull/${PR_NUMBER}/head" + git checkout --detach FETCH_HEAD + git reset --hard FETCH_HEAD + test "$(git rev-parse HEAD)" = "$HEAD_SHA" + git remote set-url origin "https://github.com/eottabom/sandbox-load-test.git" - name: Check Claude login status id: claude_auth @@ -56,6 +62,8 @@ jobs: unset CI unset GITHUB_ACTIONS unset GITHUB_TOKEN + set +e + set -o pipefail bash "${{ vars.RUNNER_WORKDIR }}/.github/scripts/claude-review-run.sh" 2>&1 | \ jq --unbuffered -r ' if .type == "assistant" then @@ -68,10 +76,15 @@ jobs: elif .type == "result" then "\n=== \(.subtype) ===" else empty end - ' 2>/dev/null || true + ' 2>/dev/null + rc="${PIPESTATUS[0]}" + set -e + exit "$rc" codex-review: - if: github.event.pull_request.draft == false + if: | + github.event.pull_request.draft == false && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association) timeout-minutes: 30 concurrency: group: codex-review-pr-${{ github.event.pull_request.number }} @@ -86,14 +99,33 @@ jobs: working-directory: ${{ vars.RUNNER_WORKDIR }} steps: - - name: Checkout PR branch + - name: Checkout PR head + env: + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git" + git fetch origin "refs/pull/${PR_NUMBER}/head" + git checkout --detach FETCH_HEAD + git reset --hard FETCH_HEAD + test "$(git rev-parse HEAD)" = "$HEAD_SHA" + git remote set-url origin "https://github.com/eottabom/sandbox-load-test.git" + + - name: Check Codex login status + id: codex_auth + env: + HOME: ${{ vars.RUNNER_HOME }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git - git fetch origin - git checkout ${{ github.event.pull_request.head.ref }} 2>/dev/null || git checkout -b ${{ github.event.pull_request.head.ref }} origin/${{ github.event.pull_request.head.ref }} - git reset --hard origin/${{ github.event.pull_request.head.ref }} + export PATH="$HOME/.local/bin:$PATH" + if codex login status 2>/dev/null | grep -qi "logged in"; then + echo "logged_in=true" >> "$GITHUB_OUTPUT" + else + echo "logged_in=false" >> "$GITHUB_OUTPUT" + gh pr comment "$PR_NUMBER" --repo eottabom/sandbox-load-test --body "⚠️ **eottabom-review-bot** self-hosted 러너의 \`codex\` CLI 로그인 세션이 만료되어 자동 리뷰를 실행할 수 없습니다. 러너 머신에서 \`codex login\`으로 재로그인해 주세요." + fi - name: Run Codex review + if: steps.codex_auth.outputs.logged_in == 'true' env: HOME: ${{ vars.RUNNER_HOME }} CI: "" diff --git a/.github/workflows/pr-mention-review.yml b/.github/workflows/pr-mention-review.yml index 479b529..df6d6d1 100644 --- a/.github/workflows/pr-mention-review.yml +++ b/.github/workflows/pr-mention-review.yml @@ -13,7 +13,8 @@ jobs: if: | contains(github.event.comment.body, '@eottabom-review-bot') && github.event.issue.pull_request != null && - github.event.comment.user.type != 'Bot' + github.event.comment.user.type != 'Bot' && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) timeout-minutes: 30 concurrency: group: claude-review-pr-${{ github.event.issue.number }} @@ -50,15 +51,19 @@ jobs: repo: context.repo.repo, pull_number: context.payload.issue.number }); - core.setOutput('branch', pr.head.ref); core.setOutput('base_ref', pr.base.ref); + core.setOutput('head_sha', pr.head.sha); - - name: Checkout PR branch + - name: Checkout PR head + env: + HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }} run: | - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git - git fetch origin - git checkout ${{ steps.pr_info.outputs.branch }} 2>/dev/null || git checkout -b ${{ steps.pr_info.outputs.branch }} origin/${{ steps.pr_info.outputs.branch }} - git reset --hard origin/${{ steps.pr_info.outputs.branch }} + git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git" + git fetch origin "refs/pull/${PR_NUMBER}/head" + git checkout --detach FETCH_HEAD + git reset --hard FETCH_HEAD + test "$(git rev-parse HEAD)" = "$HEAD_SHA" + git remote set-url origin "https://github.com/eottabom/sandbox-load-test.git" - name: Check Claude login status id: claude_auth @@ -85,6 +90,7 @@ jobs: unset CI unset GITHUB_ACTIONS unset GITHUB_TOKEN + set +e bash "${{ vars.RUNNER_WORKDIR }}/.github/scripts/claude-review-run.sh" 2>&1 | \ jq --unbuffered -r ' if .type == "assistant" then @@ -97,13 +103,17 @@ jobs: elif .type == "result" then "\n=== \(.subtype) ===" else empty end - ' 2>/dev/null || true + ' 2>/dev/null + rc="${PIPESTATUS[0]}" + set -e + exit "$rc" codex-review: if: | contains(github.event.comment.body, '@eottabom-review-bot') && github.event.issue.pull_request != null && - github.event.comment.user.type != 'Bot' + github.event.comment.user.type != 'Bot' && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) timeout-minutes: 30 concurrency: group: codex-review-pr-${{ github.event.issue.number }} @@ -140,17 +150,36 @@ jobs: repo: context.repo.repo, pull_number: context.payload.issue.number }); - core.setOutput('branch', pr.head.ref); core.setOutput('base_ref', pr.base.ref); + core.setOutput('head_sha', pr.head.sha); + + - name: Checkout PR head + env: + HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }} + run: | + git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git" + git fetch origin "refs/pull/${PR_NUMBER}/head" + git checkout --detach FETCH_HEAD + git reset --hard FETCH_HEAD + test "$(git rev-parse HEAD)" = "$HEAD_SHA" + git remote set-url origin "https://github.com/eottabom/sandbox-load-test.git" - - name: Checkout PR branch + - name: Check Codex login status + id: codex_auth + env: + HOME: ${{ vars.RUNNER_HOME }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/eottabom/sandbox-load-test.git - git fetch origin - git checkout ${{ steps.pr_info.outputs.branch }} 2>/dev/null || git checkout -b ${{ steps.pr_info.outputs.branch }} origin/${{ steps.pr_info.outputs.branch }} - git reset --hard origin/${{ steps.pr_info.outputs.branch }} + export PATH="$HOME/.local/bin:$PATH" + if codex login status 2>/dev/null | grep -qi "logged in"; then + echo "logged_in=true" >> "$GITHUB_OUTPUT" + else + echo "logged_in=false" >> "$GITHUB_OUTPUT" + gh pr comment "$PR_NUMBER" --repo eottabom/sandbox-load-test --body "⚠️ **eottabom-review-bot** self-hosted 러너의 \`codex\` CLI 로그인 세션이 만료되어 재리뷰를 실행할 수 없습니다. 러너 머신에서 \`codex login\`으로 재로그인해 주세요." + fi - name: Run Codex review + if: steps.codex_auth.outputs.logged_in == 'true' env: HOME: ${{ vars.RUNNER_HOME }} CI: ""