From 0883b9a8fe3fbda912b136fd8b4577554d263dea Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 20:13:03 -0400 Subject: [PATCH] fix(ci): use self-hosted Claude binary for ai review --- .github/workflows/ai-review.yml | 20 +++++++++++ .../scripts/github/ai-review-workflow.test.ts | 36 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/unit/scripts/github/ai-review-workflow.test.ts diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 9fa58a7e..64308f6c 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -442,6 +442,25 @@ jobs: fi done < "$REVIEW_SCOPE_MANIFEST_FILE" + - name: Resolve self-hosted Claude executable + id: toolchain + run: | + echo "claude_path=" >> "$GITHUB_OUTPUT" + + if [ "${{ needs.prepare.outputs.contributor_source }}" != "internal" ]; then + exit 0 + fi + + CLAUDE_PATH="/root/.local/bin/claude" + + if [ ! -x "$CLAUDE_PATH" ]; then + echo "::error::Missing self-hosted Claude executable at $CLAUDE_PATH" + exit 1 + fi + + "$CLAUDE_PATH" --version + echo "claude_path=$CLAUDE_PATH" >> "$GITHUB_OUTPUT" + - name: Run Claude Code Review id: claude-review timeout-minutes: ${{ fromJSON(needs.prepare.outputs.claude_timeout_minutes) }} @@ -453,6 +472,7 @@ jobs: display_report: false # Keep all public review output on the normalized comment path show_full_output: false # Keep scratch output out of public logs track_progress: false # Disabled - no progress comments, just final review + path_to_claude_code_executable: ${{ steps.toolchain.outputs.claude_path }} prompt: | REPO: ${{ github.repository }} PR NUMBER: ${{ needs.prepare.outputs.pr_number }} diff --git a/tests/unit/scripts/github/ai-review-workflow.test.ts b/tests/unit/scripts/github/ai-review-workflow.test.ts new file mode 100644 index 00000000..eb5564b5 --- /dev/null +++ b/tests/unit/scripts/github/ai-review-workflow.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from 'bun:test'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as yaml from 'js-yaml'; + +function loadWorkflow() { + const workflowPath = path.resolve(import.meta.dir, '../../../../.github/workflows/ai-review.yml'); + return yaml.load(fs.readFileSync(workflowPath, 'utf8')) as { + jobs: { + review: { + steps: Array>; + }; + }; + }; +} + +describe('ai-review workflow', () => { + test('uses the self-hosted Claude binary instead of reinstalling it on internal PRs', () => { + const workflow = loadWorkflow(); + const steps = workflow.jobs.review.steps; + + const toolchainStep = steps.find((step) => step.id === 'toolchain'); + expect(toolchainStep).toBeDefined(); + expect(toolchainStep?.name).toBe('Resolve self-hosted Claude executable'); + expect(toolchainStep?.run).toContain('CLAUDE_PATH="/root/.local/bin/claude"'); + expect(toolchainStep?.run).toContain('Missing self-hosted Claude executable'); + expect(toolchainStep?.run).toContain('echo "claude_path=$CLAUDE_PATH" >> "$GITHUB_OUTPUT"'); + + const claudeReviewStep = steps.find((step) => step.id === 'claude-review'); + expect(claudeReviewStep).toBeDefined(); + expect(claudeReviewStep?.uses).toBe('anthropics/claude-code-action@v1'); + expect(claudeReviewStep?.with?.path_to_claude_code_executable).toBe( + '${{ steps.toolchain.outputs.claude_path }}' + ); + }); +});