Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 61 additions & 9 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
name: Claude Code Review

on:
pull_request:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCKING — Fork PRs fail hard instead of skipping gracefully.

pull_request fires for fork PRs. Secrets are (correctly) withheld by GitHub, so there's no security leak. But the job still runs and fails at the create-github-app-token step because secrets.CLOSEDLOOP_APP_ID_STAGE is empty. This creates a red X check on every fork PR.

If branch protection requires this check to pass, fork PRs become unmergeable. Even without branch protection, it's confusing UX.

Fix — graceful skip for forks:

# In the job if condition, add a fork guard to the pull_request branch:
(github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository)

Fork PRs would then skip this job entirely (green skip, not red failure). Fork reviews would only happen via workflow_dispatch by a collaborator.

types: [opened]
pull_request_target:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensure this does not cause a double review

types: [opened]
workflow_dispatch:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCKING — Manual triggers can't target a specific PR.

workflow_dispatch passes the if condition, but there are no inputs:. When manually triggered:

  • github.event.pull_request is empty → checkout falls back to github.ref (default branch)
  • The Claude prompt becomes /code-review:start --github with no PR number
  • Summary posting skips (PR_NUMBER is empty)

Fix:

workflow_dispatch:
  inputs:
    pr_number:
      description: 'PR number to review'
      required: true
      type: number

Then update the checkout ref and prompt to use github.event.inputs.pr_number when event_name == 'workflow_dispatch'. Resolve the PR's head SHA via gh pr view in a setup step.


# Cancel in-progress runs for the same PR to prevent duplicate reviews
concurrency:
group: claude-review-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
claude-review:
# Filter by PR author or allow bot actors like Dependabot
Expand All @@ -32,7 +31,7 @@ jobs:
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.CLOSEDLOOP_APP_ID_STAGE }}
private-key: ${{ secrets.CLOSEDLOOP_APP_SECRET_STAGE }}
Expand Down Expand Up @@ -80,10 +79,40 @@ jobs:
run: |
git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/"

- name: Install Claude CLI
shell: bash
run: |
set -e
# claude.ai/install.sh redirects here; use direct URL to avoid Cloudflare bot challenge in CI
for i in 1 2 3; do
echo "Attempt $i: Installing Claude CLI..."
if curl -fsSL https://downloads.claude.ai/claude-code-releases/bootstrap.sh | bash; then
break
fi
[ "$i" -eq 3 ] && { echo "::error::Failed to install Claude CLI"; exit 1; }
sleep 5
done
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Verify Claude CLI
shell: bash
run: |
if ! command -v claude &> /dev/null; then
echo "::error::Claude CLI not found in PATH"
exit 1
fi
claude --version

- name: Ensure ClosedLoop Workspace
shell: bash
run: mkdir -p .closedloop-ai

- name: Run Claude Code Review
id: claude-review
continue-on-error: true
uses: anthropics/claude-code-action@v1
env:
CR_GLOBAL_CACHE: 1
with:
github_token: ${{ steps.generate_token.outputs.token }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_NEW }}
Expand Down Expand Up @@ -113,7 +142,7 @@ jobs:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
shell: bash
run: |
THREADS_FILE=".claude/code-review-threads.json"
THREADS_FILE=".closedloop-ai/code-review-threads.json"
if [ ! -f "$THREADS_FILE" ]; then
echo "No threads file — skipping."
exit 0
Expand All @@ -132,7 +161,7 @@ jobs:
GITHUB_REPOSITORY: ${{ github.repository }}
shell: bash
run: |
FINDINGS_FILE=".claude/code-review-findings.json"
FINDINGS_FILE=".closedloop-ai/code-review-findings.json"
if [ ! -f "$FINDINGS_FILE" ]; then
echo "No findings file — skipping."
exit 0
Expand All @@ -144,6 +173,29 @@ jobs:
fi
python3 "$HELPERS" post-comments --findings "$FINDINGS_FILE"

- name: Validate migrated review artifacts
id: validate-review-artifacts
if: always() && steps.claude-review.outcome == 'success'
shell: bash
run: |
ARTIFACT_COUNT=0
for FILE in \
".closedloop-ai/code-review-findings.json" \
".closedloop-ai/code-review-threads.json" \
".closedloop-ai/code-review-summary.md"; do
if [ -f "$FILE" ]; then
ARTIFACT_COUNT=$((ARTIFACT_COUNT + 1))
fi
done

if [ "$ARTIFACT_COUNT" -eq 0 ]; then
echo "::warning::Claude review completed successfully but produced no .closedloop-ai/code-review-* artifacts."
echo "::warning::This usually means the runtime/plugin path contract is out of sync."
echo "missing=true" >> "$GITHUB_OUTPUT"
else
echo "missing=false" >> "$GITHUB_OUTPUT"
fi

- name: Post code review summary
id: post-summary
if: always()
Expand All @@ -160,7 +212,7 @@ jobs:
exit 0
fi

SUMMARY_FILE=".claude/code-review-summary.md"
SUMMARY_FILE=".closedloop-ai/code-review-summary.md"

if [ -f "$SUMMARY_FILE" ]; then
SUMMARY=$(cat "$SUMMARY_FILE")
Expand Down
50 changes: 50 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH — No actor protection. Any GitHub user can burn API credits via @claude.

issue_comment runs from the base branch with secrets — fork authors can't modify this workflow. But any GitHub user can comment @claude on any issue/PR and trigger Claude Code using the org's CLAUDE_CODE_OAUTH_TOKEN.

Fix — restrict to collaborators:

if: |
  (
    github.event_name == 'issue_comment' &&
    contains(github.event.comment.body, '@claude') &&
    contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
  ) || ...

author_association is on the event payload — no API call needed. This ensures only repo collaborators can invoke @claude.

if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'

# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr *)'

Loading