summaryrefslogtreecommitdiff
path: root/.github/actions/claude-code-runner
diff options
context:
space:
mode:
authorHarsh Aggarwal (NVIDIA) <haaggarwal@nvidia.com>2025-08-05 19:28:55 +0530
committerGitHub <noreply@github.com>2025-08-05 13:58:55 +0000
commit2d775b54d2ab7772785c2196075d4c7c174407ab (patch)
treea1204b30debeabe30e61a1cc7533d94eda95b904 /.github/actions/claude-code-runner
parent9a16700e858fc0379e551ab72188eb63a54ad3f1 (diff)
Bring back hooks for auto formatting and ensure build works (#7811)
* Claude code : refactor and improve stability by using hooks * format code (#27) Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com> --------- Co-authored-by: szihs <675653+szihs@users.noreply.github.com> Co-authored-by: slangbot <ellieh+slangbot@nvidia.com> Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com>
Diffstat (limited to '.github/actions/claude-code-runner')
-rw-r--r--.github/actions/claude-code-runner/action.yml359
1 files changed, 359 insertions, 0 deletions
diff --git a/.github/actions/claude-code-runner/action.yml b/.github/actions/claude-code-runner/action.yml
new file mode 100644
index 000000000..f68a7914c
--- /dev/null
+++ b/.github/actions/claude-code-runner/action.yml
@@ -0,0 +1,359 @@
+name: "Claude Code Runner"
+description: "Complete Claude Code execution with authentication, setup, execution, and results handling"
+inputs:
+ # Authentication inputs
+ llmgw-id:
+ description: "LLMGW ID for token generation"
+ required: true
+ llmgw-secret:
+ description: "LLMGW secret for token generation"
+ required: true
+ llmgw-token-url:
+ description: "LLMGW token URL for authentication"
+ required: true
+ github-token-fallback:
+ description: "Fallback GitHub token if App token fails"
+ required: false
+ default: ""
+
+ # Claude configuration
+ model:
+ description: "Anthropic model to use"
+ required: false
+ default: "claude-3-5-sonnet-20241022"
+ max-turns:
+ description: "Maximum number of turns for Claude"
+ required: false
+ default: "50000"
+ timeout-minutes:
+ description: "Timeout for Claude action in minutes"
+ required: false
+ default: "600"
+ trigger-phrase:
+ description: "Trigger phrase to activate Claude"
+ required: false
+ default: "@claude"
+ assignee-trigger:
+ description: "Assignee trigger name"
+ required: false
+ default: "claude"
+
+ # Environment and setup
+ custom-instructions:
+ description: "Custom instructions for Claude"
+ required: true
+ mcp-config:
+ description: "MCP server configuration JSON"
+ required: false
+ default: ""
+ allowed-tools:
+ description: "Comma-separated list of allowed tools"
+ required: false
+ default: "Bash,View,GlobTool,GrepTool,BatchTool,Write"
+ setup-commands:
+ description: "Setup commands to run before Claude (multiline string)"
+ required: false
+ default: ""
+ continue-on-setup-error:
+ description: "Continue if setup commands fail"
+ required: false
+ default: "false"
+
+ # AWS/Bedrock configuration
+ use-bedrock:
+ description: "Use AWS Bedrock for Claude"
+ required: false
+ default: "true"
+ aws-region:
+ description: "AWS region"
+ required: false
+ default: ""
+ bedrock-base-url:
+ description: "Anthropic Bedrock base URL"
+ required: false
+ default: ""
+ small-fast-model:
+ description: "Small fast model for Anthropic"
+ required: false
+ default: ""
+
+outputs:
+ auth-token:
+ description: "Generated authentication token"
+ value: ${{ steps.auth-token.outputs.token }}
+ github-token:
+ description: "Final GitHub token (App or fallback)"
+ value: ${{ steps.auth-config.outputs.github-token }}
+ token-expires:
+ description: "Token expiration time (if available)"
+ value: ${{ steps.auth-token.outputs.token-expires }}
+ github-app-token-outcome:
+ description: "Outcome of GitHub App token generation"
+ value: ${{ steps.github-app-token.outcome }}
+ claude-outcome:
+ description: "Outcome of Claude Code execution"
+ value: ${{ steps.claude-action.outcome }}
+
+runs:
+ using: "composite"
+ steps:
+ # Validate environment and inputs
+ - name: Validate Environment
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ # Check required secrets
+ if [ -z "${{ inputs.llmgw-id }}" ] || [ -z "${{ inputs.llmgw-secret }}" ] || [ -z "${{ inputs.llmgw-token-url }}" ]; then
+ echo "::error::Missing required secrets: LLMGW_ID or LLMGW_SECRET or LLMGW_TOKEN_URL"
+ exit 1
+ fi
+
+ # Install required tools
+ command -v jq >/dev/null 2>&1 || { echo "::error::jq is required but not installed"; exit 1; }
+ command -v curl >/dev/null 2>&1 || { echo "::error::curl is required but not installed"; exit 1; }
+
+ echo "โœ… Environment validation passed"
+
+ # Generate custom auth token
+ - name: Generate Custom Auth Token
+ id: auth-token
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ echo "๐Ÿ” Generating authentication token..."
+
+ # Set up error handling
+ cleanup() {
+ local exit_code=$?
+ echo "๐Ÿงน Cleaning up temporary files..."
+ rm -f /tmp/token_response.json 2>/dev/null || true
+ if [ $exit_code -ne 0 ]; then
+ echo "::error::Authentication failed - check your credentials and endpoint"
+ fi
+ exit $exit_code
+ }
+ trap cleanup EXIT
+
+ # Generate token with comprehensive error handling (using Basic auth like original)
+ HTTP_CODE=$(curl -s -w "%{http_code}" -o /tmp/token_response.json --fail-with-body \
+ --max-time 30 \
+ --retry 3 \
+ --retry-delay 2 \
+ --location "${{ inputs.llmgw-token-url }}" \
+ --header 'Content-Type: application/x-www-form-urlencoded' \
+ --header "Authorization: Basic $(echo -n ${{ inputs.llmgw-id }}:${{ inputs.llmgw-secret }} | base64 -w0)" \
+ --data-urlencode 'grant_type=client_credentials' \
+ --data-urlencode 'scope=awsanthropic-readwrite azureopenai-readwrite' \
+ 2>/dev/null)
+
+ # Check HTTP response code
+ if [ "$HTTP_CODE" -ne 200 ]; then
+ echo "::error::Authentication failed with HTTP code: $HTTP_CODE"
+ if [ -f /tmp/token_response.json ]; then
+ echo "::error::Response: $(cat /tmp/token_response.json | head -c 200)"
+ fi
+ exit 1
+ fi
+
+ # Extract and validate token
+ if [ ! -f /tmp/token_response.json ]; then
+ echo "::error::No response file generated"
+ exit 1
+ fi
+
+ ANTHROPIC_AUTH_TOKEN=$(jq -r '.access_token // empty' /tmp/token_response.json 2>/dev/null)
+
+ # Validate token format and length
+ if [ -z "$ANTHROPIC_AUTH_TOKEN" ] || [ "$ANTHROPIC_AUTH_TOKEN" = "null" ]; then
+ echo "::error::Failed to extract access token from response"
+ exit 1
+ fi
+
+ # Basic token validation
+ if [ ${#ANTHROPIC_AUTH_TOKEN} -lt 10 ]; then
+ echo "::error::Token appears to be too short (${#ANTHROPIC_AUTH_TOKEN} characters)"
+ exit 1
+ fi
+
+ # CRITICAL: Mask the token BEFORE any output
+ echo "::add-mask::$ANTHROPIC_AUTH_TOKEN"
+
+ # Set outputs
+ echo "token=$ANTHROPIC_AUTH_TOKEN" >> $GITHUB_OUTPUT
+
+ # Set token expiry if available
+ TOKEN_EXPIRES=$(jq -r '.expires_in // empty' /tmp/token_response.json 2>/dev/null)
+ if [ -n "$TOKEN_EXPIRES" ]; then
+ echo "::add-mask::$TOKEN_EXPIRES"
+ echo "token-expires=$TOKEN_EXPIRES" >> $GITHUB_OUTPUT
+ fi
+
+ echo "โœ… Authentication token generated and masked successfully"
+
+ # Clean up response file
+ rm -f /tmp/token_response.json
+
+ # Configure authentication
+ - name: Configure Authentication
+ id: auth-config
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ # Use GitHub App token if available, otherwise use GITHUB_TOKEN
+ if [ -n "${{ steps.github-app-token.outputs.token }}" ]; then
+ echo "github-token=${{ steps.github-app-token.outputs.token }}" >> $GITHUB_OUTPUT
+ echo "โœ… Using GitHub App authentication"
+ else
+ echo "github-token=${{ inputs.github-token-fallback }}" >> $GITHUB_OUTPUT
+ echo "โš ๏ธ Using fallback GITHUB_TOKEN authentication"
+ fi
+
+ # Run setup commands if provided
+ - name: Run setup commands
+ id: setup-commands
+ if: inputs.setup-commands != ''
+ shell: bash
+ continue-on-error: ${{ inputs.continue-on-setup-error == 'true' }}
+ run: ${{ inputs.setup-commands }}
+
+ # Security cleanup
+ - name: Security Cleanup
+ if: always()
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ echo "๐Ÿงน Performing security cleanup..."
+
+ # Clear any temporary files that might contain sensitive data
+ find /tmp -name "*token*" -type f -delete 2>/dev/null || true
+ find /tmp -name "*auth*" -type f -delete 2>/dev/null || true
+ find /tmp -name "*response*" -type f -delete 2>/dev/null || true
+
+ # Clear environment variables (belt and suspenders approach)
+ unset ANTHROPIC_API_KEY 2>/dev/null || true
+ unset ANTHROPIC_AUTH_TOKEN 2>/dev/null || true
+
+ echo "โœ… Security cleanup completed"
+
+ # Workflow summary
+ - name: Generate Workflow Summary
+ if: always()
+ shell: bash
+ run: |
+ echo "## Claude Code Runner Summary" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Authentication" >> $GITHUB_STEP_SUMMARY
+ echo "- **Auth Token**: โœ… Generated" >> $GITHUB_STEP_SUMMARY
+ echo "- **Token Expiry**: ${{ steps.auth-token.outputs.token-expires || 'Not provided' }}" >> $GITHUB_STEP_SUMMARY
+ echo "- **GitHub Token**: ${{ steps.github-app-token.outcome == 'success' && 'โœ… GitHub App' || 'โš ๏ธ Fallback' }}" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Configuration" >> $GITHUB_STEP_SUMMARY
+ echo "- **Model**: ${{ inputs.model }}" >> $GITHUB_STEP_SUMMARY
+ echo "- **Max Turns**: ${{ inputs.max-turns }}" >> $GITHUB_STEP_SUMMARY
+ echo "- **Timeout**: ${{ inputs.timeout-minutes }} minutes" >> $GITHUB_STEP_SUMMARY
+ echo "- **Bedrock**: ${{ inputs.use-bedrock == 'true' && 'โœ… Enabled' || 'โŒ Disabled' }}" >> $GITHUB_STEP_SUMMARY
+ if [ "${{ inputs.use-bedrock }}" = "true" ]; then
+ echo "- **AWS Region**: ${{ inputs.aws-region || 'Default' }}" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Setup" >> $GITHUB_STEP_SUMMARY
+ echo "- **Setup Commands**: ${{ inputs.setup-commands != '' && 'โœ… Executed' || 'โญ๏ธ Skipped' }}" >> $GITHUB_STEP_SUMMARY
+ if [ "${{ inputs.setup-commands }}" != "" ]; then
+ echo "- **Setup Result**: ${{ steps.setup-commands.outcome || 'Unknown' }}" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "- **Security Cleanup**: โœ… Completed" >> $GITHUB_STEP_SUMMARY
+
+ # Execute Claude Code Action
+ - name: Execute Claude Code Action
+ id: claude-action
+ uses: anthropics/claude-code-action@beta
+ with:
+ custom_instructions: ${{ inputs.custom-instructions }}
+ mcp_config: ${{ inputs.mcp-config }}
+ allowed_tools: ${{ inputs.allowed-tools }}
+ trigger_phrase: ${{ inputs.trigger-phrase }}
+ assignee_trigger: ${{ inputs.assignee-trigger }}
+ timeout_minutes: ${{ inputs.timeout-minutes }}
+ github_token: ${{ steps.auth-config.outputs.github-token }}
+ use_bedrock: ${{ inputs.use-bedrock }}
+ model: ${{ inputs.model }}
+ max_turns: ${{ inputs.max-turns }}
+ # Use claude_env for custom environment variables
+ claude_env: |
+ ANTHROPIC_BEDROCK_BASE_URL: ${{ inputs.bedrock-base-url }}
+ ANTHROPIC_SMALL_FAST_MODEL: ${{ inputs.small-fast-model }}
+ AWS_REGION: ${{ inputs.aws-region }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_EVENT_NAME: ${{ github.event_name }}
+ GITHUB_ACTOR: ${{ github.actor }}
+ ANTHROPIC_AUTH_TOKEN: ${{ steps.auth-token.outputs.token }}
+ DISABLE_TELEMETRY: 1
+ continue-on-error: true
+
+ # Handle Claude results
+ - name: Handle Claude Results
+ if: always()
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ echo "๐Ÿ” Processing Claude action results..."
+
+ # Check if Claude action succeeded
+ if [ "${{ steps.claude-action.outcome }}" = "success" ]; then
+ echo "โœ… Claude Code action completed successfully"
+ elif [ "${{ steps.claude-action.outcome }}" = "failure" ]; then
+ echo "โŒ Claude Code action failed"
+
+ # Create error summary
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### โŒ Claude Execution Failed" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "The Claude Code action encountered an error. Common causes:" >> $GITHUB_STEP_SUMMARY
+ echo "- Authentication issues" >> $GITHUB_STEP_SUMMARY
+ echo "- Network connectivity problems" >> $GITHUB_STEP_SUMMARY
+ echo "- Model availability issues" >> $GITHUB_STEP_SUMMARY
+ echo "- Rate limiting" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "Please check the workflow logs for detailed error information." >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โš ๏ธ Claude Code action was cancelled or skipped"
+ fi
+
+ # Add execution summary
+ - name: Add Claude Execution Summary
+ if: always()
+ shell: bash
+ run: |
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Claude Execution Details" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ # Event information
+ echo "#### Event Information" >> $GITHUB_STEP_SUMMARY
+ echo "- **Trigger**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
+ echo "- **Repository**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
+ echo "- **Actor**: @${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ # Execution status
+ echo "#### Execution Status" >> $GITHUB_STEP_SUMMARY
+ if [ "${{ steps.claude-action.outcome }}" = "success" ]; then
+ echo "- **Claude Action**: โœ… Success" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ steps.claude-action.outcome }}" = "failure" ]; then
+ echo "- **Claude Action**: โŒ Failed" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "- **Claude Action**: โš ๏ธ ${{ steps.claude-action.outcome }}" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "- **Model Used**: ${{ inputs.model }}" >> $GITHUB_STEP_SUMMARY
+ echo "- **Max Turns**: ${{ inputs.max-turns }}" >> $GITHUB_STEP_SUMMARY
+ echo "- **Workflow Status**: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
+
+ # Add timestamp
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "#### Timestamp" >> $GITHUB_STEP_SUMMARY
+ echo "- **Completed at**: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY