name: "Claude Code Action Official" description: "General-purpose Claude agent for GitHub PRs and issues. Can answer questions and implement code changes." branding: icon: "at-sign" color: "orange" inputs: trigger_phrase: description: "The trigger phrase to look for in comments or issue body" required: false default: "@claude" assignee_trigger: description: "The assignee username that triggers the action (e.g. @claude)" required: false label_trigger: description: "The label that triggers the action (e.g. claude)" required: false default: "claude" base_branch: description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)" required: false branch_prefix: description: "The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format)" required: false default: "claude/" # Claude Code configuration model: description: "Model to use (provider-specific format required for Bedrock/Vertex)" required: false anthropic_model: description: "DEPRECATED: Use 'model' instead. Model to use (provider-specific format required for Bedrock/Vertex)" required: false fallback_model: description: "Enable automatic fallback to specified model when primary model is unavailable" required: false allowed_tools: description: "Additional tools for Claude to use (the base GitHub tools will always be included)" required: false default: "" disallowed_tools: description: "Tools that Claude should never use" required: false default: "" custom_instructions: description: "Additional custom instructions to include in the prompt for Claude" required: false default: "" direct_prompt: description: "Direct instruction for Claude (bypasses normal trigger detection)" required: false default: "" override_prompt: description: "Complete replacement of Claude's prompt with custom template (supports variable substitution)" required: false default: "" mcp_config: description: "Additional MCP configuration (JSON string) that merges with the built-in GitHub MCP servers" additional_permissions: description: "Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results" required: false default: "" claude_env: description: "Custom environment variables to pass to Claude Code execution (YAML format)" required: false default: "" settings: description: "Claude Code settings as JSON string or path to settings JSON file" required: false default: "" # Auth configuration anthropic_api_key: description: "Anthropic API key (required for direct API, not needed for Bedrock/Vertex)" required: false claude_code_oauth_token: description: "Claude Code OAuth token (alternative to anthropic_api_key)" required: false github_token: description: "GitHub token with repo and pull request permissions (optional if using GitHub App)" required: false use_bedrock: description: "Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API" required: false default: "false" use_vertex: description: "Use Google Vertex AI with OIDC authentication instead of direct Anthropic API" required: false default: "false" max_turns: description: "Maximum number of conversation turns" required: false default: "" timeout_minutes: description: "Timeout in minutes for execution" required: false default: "30" use_sticky_comment: description: "Use just one comment to deliver issue/PR comments" required: false default: "false" use_commit_signing: description: "Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands" required: false default: "false" experimental_allowed_domains: description: "Restrict network access to these domains only (newline-separated). If not set, no restrictions are applied. Provider domains are auto-detected." required: false default: "" outputs: execution_file: description: "Path to the Claude Code execution output file" value: ${{ steps.claude-code.outputs.execution_file }} branch_name: description: "The branch created by Claude Code for this execution" value: ${{ steps.prepare.outputs.CLAUDE_BRANCH }} runs: using: "composite" steps: - name: Install Bun uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2 with: bun-version: 1.2.11 - name: Install Dependencies shell: bash run: | cd ${GITHUB_ACTION_PATH} bun install - name: Prepare action id: prepare shell: bash run: | bun run ${GITHUB_ACTION_PATH}/src/entrypoints/prepare.ts env: TRIGGER_PHRASE: ${{ inputs.trigger_phrase }} ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }} LABEL_TRIGGER: ${{ inputs.label_trigger }} BASE_BRANCH: ${{ inputs.base_branch }} BRANCH_PREFIX: ${{ inputs.branch_prefix }} ALLOWED_TOOLS: ${{ inputs.allowed_tools }} DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }} CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }} DIRECT_PROMPT: ${{ inputs.direct_prompt }} OVERRIDE_PROMPT: ${{ inputs.override_prompt }} MCP_CONFIG: ${{ inputs.mcp_config }} OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }} GITHUB_RUN_ID: ${{ github.run_id }} USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }} ACTIONS_TOKEN: ${{ github.token }} ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} - name: Install Base Action Dependencies if: steps.prepare.outputs.contains_trigger == 'true' shell: bash run: | echo "Installing base-action dependencies..." cd ${GITHUB_ACTION_PATH}/base-action bun install echo "Base-action dependencies installed" - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' shell: bash run: | SQUID_START_TIME=$(date +%s.%N) # Create whitelist file echo "${{ inputs.experimental_allowed_domains }}" > $RUNNER_TEMP/whitelist.txt # Ensure each domain has proper format # If domain doesn't start with a dot and isn't an IP, add the dot for subdomain matching mv $RUNNER_TEMP/whitelist.txt $RUNNER_TEMP/whitelist.txt.orig while IFS= read -r domain; do if [ -n "$domain" ]; then # Trim whitespace domain=$(echo "$domain" | xargs) # If it's not empty and doesn't start with a dot, add one if [[ "$domain" != .* ]] && [[ ! "$domain" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo ".$domain" >> $RUNNER_TEMP/whitelist.txt else echo "$domain" >> $RUNNER_TEMP/whitelist.txt fi fi done < $RUNNER_TEMP/whitelist.txt.orig # Create Squid config with whitelist echo "http_port 3128" > $RUNNER_TEMP/squid.conf echo "" >> $RUNNER_TEMP/squid.conf echo "# Define ACLs" >> $RUNNER_TEMP/squid.conf echo "acl whitelist dstdomain \"/etc/squid/whitelist.txt\"" >> $RUNNER_TEMP/squid.conf echo "acl localnet src 127.0.0.1/32" >> $RUNNER_TEMP/squid.conf echo "acl localnet src 172.17.0.0/16" >> $RUNNER_TEMP/squid.conf echo "acl SSL_ports port 443" >> $RUNNER_TEMP/squid.conf echo "acl Safe_ports port 80" >> $RUNNER_TEMP/squid.conf echo "acl Safe_ports port 443" >> $RUNNER_TEMP/squid.conf echo "acl CONNECT method CONNECT" >> $RUNNER_TEMP/squid.conf echo "" >> $RUNNER_TEMP/squid.conf echo "# Deny requests to certain unsafe ports" >> $RUNNER_TEMP/squid.conf echo "http_access deny !Safe_ports" >> $RUNNER_TEMP/squid.conf echo "" >> $RUNNER_TEMP/squid.conf echo "# Only allow CONNECT to SSL ports" >> $RUNNER_TEMP/squid.conf echo "http_access deny CONNECT !SSL_ports" >> $RUNNER_TEMP/squid.conf echo "" >> $RUNNER_TEMP/squid.conf echo "# Allow localhost" >> $RUNNER_TEMP/squid.conf echo "http_access allow localhost" >> $RUNNER_TEMP/squid.conf echo "" >> $RUNNER_TEMP/squid.conf echo "# Allow localnet access to whitelisted domains" >> $RUNNER_TEMP/squid.conf echo "http_access allow localnet whitelist" >> $RUNNER_TEMP/squid.conf echo "" >> $RUNNER_TEMP/squid.conf echo "# Deny everything else" >> $RUNNER_TEMP/squid.conf echo "http_access deny all" >> $RUNNER_TEMP/squid.conf echo "Starting Squid proxy..." # First, remove any existing container sudo docker rm -f squid-proxy 2>/dev/null || true # Ensure whitelist file is not empty (Squid fails with empty files) if [ ! -s "$RUNNER_TEMP/whitelist.txt" ]; then echo "WARNING: Whitelist file is empty, adding a dummy entry" echo ".example.com" >> $RUNNER_TEMP/whitelist.txt fi # Use sudo to prevent Claude from stopping the container CONTAINER_ID=$(sudo docker run -d \ --name squid-proxy \ -p 127.0.0.1:3128:3128 \ -v $RUNNER_TEMP/squid.conf:/etc/squid/squid.conf:ro \ -v $RUNNER_TEMP/whitelist.txt:/etc/squid/whitelist.txt:ro \ ubuntu/squid:latest 2>&1) || { echo "ERROR: Failed to start Squid container" exit 1 } # Wait for proxy to be ready (usually < 1 second) READY=false for i in {1..30}; do if nc -z 127.0.0.1 3128 2>/dev/null; then TOTAL_TIME=$(echo "scale=3; $(date +%s.%N) - $SQUID_START_TIME" | bc) echo "Squid proxy ready in ${TOTAL_TIME}s" READY=true break fi sleep 0.1 done if [ "$READY" != "true" ]; then echo "ERROR: Squid proxy failed to start within 3 seconds" echo "Container logs:" sudo docker logs squid-proxy 2>&1 || true echo "Container status:" sudo docker ps -a | grep squid-proxy || true exit 1 fi # Set proxy environment variables echo "http_proxy=http://127.0.0.1:3128" >> $GITHUB_ENV echo "https_proxy=http://127.0.0.1:3128" >> $GITHUB_ENV echo "HTTP_PROXY=http://127.0.0.1:3128" >> $GITHUB_ENV echo "HTTPS_PROXY=http://127.0.0.1:3128" >> $GITHUB_ENV - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' shell: bash run: | # Install Claude Code globally bun install -g @anthropic-ai/claude-code@1.0.59 # Run the base-action bun run ${GITHUB_ACTION_PATH}/base-action/src/index.ts env: # Base-action inputs CLAUDE_CODE_ACTION: "1" INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt INPUT_ALLOWED_TOOLS: ${{ env.ALLOWED_TOOLS }} INPUT_DISALLOWED_TOOLS: ${{ env.DISALLOWED_TOOLS }} INPUT_MAX_TURNS: ${{ inputs.max_turns }} INPUT_MCP_CONFIG: ${{ steps.prepare.outputs.mcp_config }} INPUT_SETTINGS: ${{ inputs.settings }} INPUT_SYSTEM_PROMPT: "" INPUT_APPEND_SYSTEM_PROMPT: "" INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} INPUT_CLAUDE_ENV: ${{ inputs.claude_env }} INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }} # Model configuration ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }} GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }} NODE_VERSION: ${{ env.NODE_VERSION }} DETAILED_PERMISSION_MESSAGES: "1" # Provider configuration ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }} CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }} ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }} CLAUDE_CODE_USE_BEDROCK: ${{ inputs.use_bedrock == 'true' && '1' || '' }} CLAUDE_CODE_USE_VERTEX: ${{ inputs.use_vertex == 'true' && '1' || '' }} # AWS configuration AWS_REGION: ${{ env.AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }} AWS_SESSION_TOKEN: ${{ env.AWS_SESSION_TOKEN }} ANTHROPIC_BEDROCK_BASE_URL: ${{ env.ANTHROPIC_BEDROCK_BASE_URL || (env.AWS_REGION && format('https://bedrock-runtime.{0}.amazonaws.com', env.AWS_REGION)) }} # GCP configuration ANTHROPIC_VERTEX_PROJECT_ID: ${{ env.ANTHROPIC_VERTEX_PROJECT_ID }} CLOUD_ML_REGION: ${{ env.CLOUD_ML_REGION }} GOOGLE_APPLICATION_CREDENTIALS: ${{ env.GOOGLE_APPLICATION_CREDENTIALS }} ANTHROPIC_VERTEX_BASE_URL: ${{ env.ANTHROPIC_VERTEX_BASE_URL }} # Model-specific regions for Vertex VERTEX_REGION_CLAUDE_3_5_HAIKU: ${{ env.VERTEX_REGION_CLAUDE_3_5_HAIKU }} VERTEX_REGION_CLAUDE_3_5_SONNET: ${{ env.VERTEX_REGION_CLAUDE_3_5_SONNET }} VERTEX_REGION_CLAUDE_3_7_SONNET: ${{ env.VERTEX_REGION_CLAUDE_3_7_SONNET }} - name: Update comment with job link if: steps.prepare.outputs.contains_trigger == 'true' && steps.prepare.outputs.claude_comment_id && always() shell: bash run: | bun run ${GITHUB_ACTION_PATH}/src/entrypoints/update-comment-link.ts env: REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }} CLAUDE_COMMENT_ID: ${{ steps.prepare.outputs.claude_comment_id }} GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }} GITHUB_EVENT_NAME: ${{ github.event_name }} TRIGGER_COMMENT_ID: ${{ github.event.comment.id }} CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }} IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }} BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }} CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }} OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }} TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }} PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }} PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }} USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} - name: Display Claude Code Report if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != '' shell: bash run: | # Try to format the turns, but if it fails, dump the raw JSON if bun run ${{ github.action_path }}/src/entrypoints/format-turns.ts "${{ steps.claude-code.outputs.execution_file }}" >> $GITHUB_STEP_SUMMARY 2>/dev/null; then echo "Successfully formatted Claude Code report" else echo "## Claude Code Report (Raw Output)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "Failed to format output (please report). Here's the raw JSON:" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo '```json' >> $GITHUB_STEP_SUMMARY cat "${{ steps.claude-code.outputs.execution_file }}" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY fi - name: Revoke app token if: always() && inputs.github_token == '' shell: bash run: | curl -L \ -X DELETE \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${{ steps.prepare.outputs.GITHUB_TOKEN }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ ${GITHUB_API_URL:-https://api.github.com}/installation/token