diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 4daeba6..ffbb774 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -4,19 +4,21 @@ # 1. AUTO — fires on every PR open/update → Claude auto-reviews the diff # 2. MANUAL — mention @claude anywhere in an issue/PR/review to invoke the agent # -# Auth: CLAUDE_CODE_OAUTH_TOKEN (repository secret) — uses Claude Pro/Max subscription. -# Generate: `claude setup-token`, then `gh secret set CLAUDE_CODE_OAUTH_TOKEN` +# Auth (pick one that works in your environment): +# - ANTHROPIC_API_KEY (repository secret) — required for Auto PR Review when your +# org disables Claude subscription access in CI (OAuth fails with that policy). +# Set: gh secret set ANTHROPIC_API_KEY -R subkoks/ +# - CLAUDE_CODE_OAUTH_TOKEN — Claude Pro/Max OAuth; may work for interactive @claude +# Generate: `claude setup-token`, then `gh secret set CLAUDE_CODE_OAUTH_TOKEN` # Setup: https://github.com/anthropics/claude-code-action/blob/main/docs/setup.md # Security: https://github.com/anthropics/claude-code-action/blob/main/docs/security.md name: Claude Code on: - # AUTO: review every PR diff as soon as it's opened or updated pull_request: types: [opened, synchronize, reopened, ready_for_review] - # MANUAL: @claude mention in issues, PRs, review threads issue_comment: types: [created] pull_request_review_comment: @@ -27,10 +29,13 @@ on: types: [submitted] jobs: - # ─── AUTO review on every PR ─────────────────────────────────────────────── claude-pr-review: name: Auto PR Review - if: github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.actor != 'dependabot[bot]' + if: | + github.event_name == 'pull_request' && + github.event.pull_request.draft == false && + github.actor != 'dependabot[bot]' && + secrets.ANTHROPIC_API_KEY != '' runs-on: ubuntu-latest permissions: contents: read @@ -44,32 +49,26 @@ jobs: with: fetch-depth: 1 - - name: Validate & normalize Claude token + - name: Validate Anthropic API key env: - RAW_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + RAW_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | - # A wrapped copy/paste embeds a newline in the secret -> the action sends - # an invalid auth header ("Header has invalid value: '***\n ***'"). Strip - # all whitespace to heal it, then sanity-check the shape and fail loudly - # if the secret is missing or clearly wrong. - TOKEN="$(printf '%s' "$RAW_TOKEN" | tr -d '[:space:]')" - if [ -z "$TOKEN" ]; then - echo "::error::CLAUDE_CODE_OAUTH_TOKEN secret is not set. Run 'claude setup-token' then 'gh secret set CLAUDE_CODE_OAUTH_TOKEN'." + KEY="$(printf '%s' "$RAW_KEY" | tr -d '[:space:]')" + if [ -z "$KEY" ]; then + echo "::error::ANTHROPIC_API_KEY is not set. Add it for Auto PR Review: gh secret set ANTHROPIC_API_KEY" exit 1 fi - case "$TOKEN" in - sk-ant-oat*) : ;; - *) echo "::error::CLAUDE_CODE_OAUTH_TOKEN is malformed (expected to start with sk-ant-oat). Re-copy from 'claude setup-token' output."; exit 1 ;; + case "$KEY" in + sk-ant-*) : ;; + *) echo "::warning::ANTHROPIC_API_KEY has an unexpected prefix; continuing anyway." ;; esac - echo "::add-mask::$TOKEN" - printf 'CLAUDE_OAUTH=%s\n' "$TOKEN" >> "$GITHUB_ENV" - echo "Claude token shape OK (length ${#TOKEN})." + echo "::add-mask::$KEY" + echo "Anthropic API key present (length ${#KEY})." - name: Claude auto-review uses: anthropics/claude-code-action@593d7a5c4e0073569f74772c2b7b64c30ec14707 # v1 with: - claude_code_oauth_token: ${{ env.CLAUDE_OAUTH }} - # Prompt sent automatically on every PR — no @claude mention needed + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} prompt: | Review this pull request. Check for: - Bugs, logic errors, or broken functionality @@ -85,14 +84,16 @@ jobs: plugins: | code-review@claude-plugins-official - # ─── MANUAL: respond to @claude mentions ─────────────────────────────────── claude-interactive: name: Interactive (@claude) if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)) + (secrets.ANTHROPIC_API_KEY != '' || secrets.CLAUDE_CODE_OAUTH_TOKEN != '') && + ( + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)) + ) runs-on: ubuntu-latest permissions: contents: write @@ -106,31 +107,39 @@ jobs: with: fetch-depth: 1 - - name: Validate & normalize Claude token + - name: Resolve Claude auth + id: auth env: - RAW_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + RAW_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + RAW_OAUTH: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} run: | - # A wrapped copy/paste embeds a newline in the secret -> the action sends - # an invalid auth header ("Header has invalid value: '***\n ***'"). Strip - # all whitespace to heal it, then sanity-check the shape and fail loudly - # if the secret is missing or clearly wrong. - TOKEN="$(printf '%s' "$RAW_TOKEN" | tr -d '[:space:]')" - if [ -z "$TOKEN" ]; then - echo "::error::CLAUDE_CODE_OAUTH_TOKEN secret is not set. Run 'claude setup-token' then 'gh secret set CLAUDE_CODE_OAUTH_TOKEN'." + API_KEY="$(printf '%s' "$RAW_API_KEY" | tr -d '[:space:]')" + OAUTH="$(printf '%s' "$RAW_OAUTH" | tr -d '[:space:]')" + if [ -n "$API_KEY" ]; then + echo "mode=api" >> "$GITHUB_OUTPUT" + echo "::add-mask::$API_KEY" + echo "Using ANTHROPIC_API_KEY for @claude." + exit 0 + fi + if [ -z "$OAUTH" ]; then + echo "::error::No Claude auth configured. Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN." exit 1 fi - case "$TOKEN" in + case "$OAUTH" in sk-ant-oat*) : ;; - *) echo "::error::CLAUDE_CODE_OAUTH_TOKEN is malformed (expected to start with sk-ant-oat). Re-copy from 'claude setup-token' output."; exit 1 ;; + *) echo "::error::CLAUDE_CODE_OAUTH_TOKEN is malformed (expected sk-ant-oat…). Re-run 'claude setup-token'."; exit 1 ;; esac - echo "::add-mask::$TOKEN" - printf 'CLAUDE_OAUTH=%s\n' "$TOKEN" >> "$GITHUB_ENV" - echo "Claude token shape OK (length ${#TOKEN})." + echo "mode=oauth" >> "$GITHUB_OUTPUT" + echo "::add-mask::$OAUTH" + printf 'CLAUDE_OAUTH=%s\n' "$OAUTH" >> "$GITHUB_ENV" + echo "Using CLAUDE_CODE_OAUTH_TOKEN for @claude (may fail if org disabled subscription access in CI)." - name: Run Claude Code id: claude + continue-on-error: true uses: anthropics/claude-code-action@593d7a5c4e0073569f74772c2b7b64c30ec14707 # v1 with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} claude_code_oauth_token: ${{ env.CLAUDE_OAUTH }} claude_args: | --max-turns 15 @@ -138,3 +147,8 @@ jobs: https://github.com/anthropics/claude-plugins-official.git plugins: | code-review@claude-plugins-official + + - name: Report Claude auth failure + if: steps.claude.outcome == 'failure' && steps.auth.outputs.mode == 'oauth' + run: | + echo "::warning::Claude OAuth failed (common when org disables subscription access in CI). Add ANTHROPIC_API_KEY: gh secret set ANTHROPIC_API_KEY -R ${{ github.repository }}"