@@ -2,36 +2,61 @@ name: "Claude Code Action"
22description : " Run Claude Code in GitHub Actions workflows"
33
44inputs :
5- github_token :
6- description : " GitHub token with repo and issues permissions"
7- required : true
85 anthropic_api_key :
96 description : " Anthropic API key"
10- required : true
11- prompt :
12- description : " The prompt to send to Claude Code"
7+ required : false
8+ github_token :
9+ description : " GitHub token for Claude to operate with"
10+ required : false
11+ default : ${{ github.token }}
12+ trigger_phrase :
13+ description : " The trigger phrase to look for in comments, issue/PR bodies, and issue titles"
14+ required : false
15+ default : " @claude"
16+ assignee_trigger :
17+ description : " The assignee username that triggers the action (e.g. @claude). Only used for issue assignment"
18+ required : false
19+ max_turns :
20+ description : " Maximum number of conversation turns Claude can take"
21+ required : false
22+ timeout_minutes :
23+ description : " Timeout in minutes for execution"
24+ required : false
25+ default : " 30"
26+ model :
27+ description : " Model to use (provider-specific format required for Bedrock/Vertex)"
28+ required : false
29+ use_bedrock :
30+ description : " Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API"
31+ required : false
32+ default : " false"
33+ use_vertex :
34+ description : " Use Google Vertex AI with OIDC authentication instead of direct Anthropic API"
35+ required : false
36+ default : " false"
37+ allowed_tools :
38+ description : " Additional tools for Claude to use (the base GitHub tools will always be included)"
1339 required : false
1440 default : " "
15- prompt_file :
16- description : " Path to a file containing the prompt to send to Claude Code "
41+ disallowed_tools :
42+ description : " Tools that Claude should never use "
1743 required : false
1844 default : " "
19- allowed_tools :
20- description : " Comma-separated list of allowed tools for Claude Code to use "
45+ custom_instructions :
46+ description : " Additional custom instructions to include in the prompt for Claude "
2147 required : false
2248 default : " "
23- output_file :
24- description : " File to save Claude Code output to (optional) "
49+ mcp_config :
50+ description : " Additional MCP configuration (JSON string) that merges with the built-in GitHub MCP servers "
2551 required : false
2652 default : " "
27- timeout_minutes :
28- description : " Timeout in minutes for Claude Code execution"
53+ claude_env :
54+ description : " Custom environment variables to pass to Claude Code execution (YAML format) "
2955 required : false
30- default : " 10 "
31- install_github_mcp :
32- description : " Whether to install the GitHub MCP server "
56+ default : " "
57+ direct_prompt :
58+ description : " Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) "
3359 required : false
34- default : " false"
3560
3661runs :
3762 using : " composite"
4166 run : npm install -g @anthropic-ai/claude-code
4267
4368 - name : Install GitHub MCP Server
44- if : inputs.install_github_mcp == 'true'
4569 shell : bash
4670 run : |
4771 claude mcp add-json github '{
@@ -59,106 +83,171 @@ runs:
5983 }
6084 }'
6185
62- - name : Prepare Prompt File
86+ - name : Extract GitHub Context and Create Prompt
6387 shell : bash
64- id : prepare_prompt
88+ id : prepare_context
6589 run : |
66- # If neither prompt nor prompt_file is provided, auto-generate from GitHub context
67- if [ -z "${{ inputs.prompt }}" ] && [ -z "${{ inputs.prompt_file }}" ]; then
68- echo "Auto-generating prompt from GitHub context..."
69- mkdir -p /tmp/claude-action
70-
71- # Extract the user's request from the GitHub event (mimicking official behavior)
72- if [[ "${{ github.event_name }}" == "issue_comment" ]]; then
73- PROMPT_TEXT="${{ github.event.comment.body }}"
74- CONTEXT="Issue Comment on: ${{ github.event.issue.title }}"
75- elif [[ "${{ github.event_name }}" == "pull_request_review_comment" ]]; then
76- PROMPT_TEXT="${{ github.event.comment.body }}"
77- CONTEXT="PR Comment on: ${{ github.event.pull_request.title }}"
78- elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then
79- PROMPT_TEXT="${{ github.event.review.body }}"
80- CONTEXT="PR Review on: ${{ github.event.pull_request.title }}"
81- elif [[ "${{ github.event_name }}" == "issues" ]]; then
82- PROMPT_TEXT="${{ github.event.issue.body }}"
83- CONTEXT="Issue: ${{ github.event.issue.title }}"
84- else
85- PROMPT_TEXT="Help with this repository"
86- CONTEXT="Repository assistance requested"
90+ echo "🔍 Extracting GitHub context from event: ${{ github.event_name }}"
91+
92+ # Function to check for trigger phrase
93+ check_trigger() {
94+ local text="$1"
95+ local trigger="${{ inputs.trigger_phrase }}"
96+ if [[ "$text" == *"$trigger"* ]]; then
97+ return 0
8798 fi
88-
89- # Create GitHub context prompt (similar to official action)
90- cat > /tmp/claude-action/github-context-prompt.txt << EOF
99+ return 1
100+ }
101+
102+ # Extract context based on event type
103+ TRIGGER_FOUND="false"
104+ USER_REQUEST=""
105+ CONTEXT_INFO=""
106+
107+ case "${{ github.event_name }}" in
108+ "issue_comment")
109+ COMMENT_BODY="${{ github.event.comment.body }}"
110+ ISSUE_TITLE="${{ github.event.issue.title }}"
111+ ISSUE_NUMBER="${{ github.event.issue.number }}"
112+
113+ if check_trigger "$COMMENT_BODY"; then
114+ TRIGGER_FOUND="true"
115+ USER_REQUEST="$COMMENT_BODY"
116+ CONTEXT_INFO="Issue Comment on #$ISSUE_NUMBER: $ISSUE_TITLE"
117+ fi
118+ ;;
119+
120+ "pull_request_review_comment")
121+ COMMENT_BODY="${{ github.event.comment.body }}"
122+ PR_TITLE="${{ github.event.pull_request.title }}"
123+ PR_NUMBER="${{ github.event.pull_request.number }}"
124+
125+ if check_trigger "$COMMENT_BODY"; then
126+ TRIGGER_FOUND="true"
127+ USER_REQUEST="$COMMENT_BODY"
128+ CONTEXT_INFO="PR Comment on #$PR_NUMBER: $PR_TITLE"
129+ fi
130+ ;;
131+
132+ "pull_request_review")
133+ REVIEW_BODY="${{ github.event.review.body }}"
134+ PR_TITLE="${{ github.event.pull_request.title }}"
135+ PR_NUMBER="${{ github.event.pull_request.number }}"
136+
137+ if check_trigger "$REVIEW_BODY"; then
138+ TRIGGER_FOUND="true"
139+ USER_REQUEST="$REVIEW_BODY"
140+ CONTEXT_INFO="PR Review on #$PR_NUMBER: $PR_TITLE"
141+ fi
142+ ;;
143+
144+ "issues")
145+ ISSUE_BODY="${{ github.event.issue.body }}"
146+ ISSUE_TITLE="${{ github.event.issue.title }}"
147+ ISSUE_NUMBER="${{ github.event.issue.number }}"
148+
149+ if check_trigger "$ISSUE_TITLE" || check_trigger "$ISSUE_BODY"; then
150+ TRIGGER_FOUND="true"
151+ USER_REQUEST="$ISSUE_BODY"
152+ CONTEXT_INFO="Issue #$ISSUE_NUMBER: $ISSUE_TITLE"
153+ elif [[ "${{ github.event.action }}" == "assigned" && -n "${{ inputs.assignee_trigger }}" ]]; then
154+ ASSIGNEE="${{ github.event.assignee.login }}"
155+ if [[ "$ASSIGNEE" == "${{ inputs.assignee_trigger }}" ]]; then
156+ TRIGGER_FOUND="true"
157+ USER_REQUEST="$ISSUE_BODY"
158+ CONTEXT_INFO="Issue #$ISSUE_NUMBER assigned to $ASSIGNEE: $ISSUE_TITLE"
159+ fi
160+ fi
161+ ;;
162+ esac
163+
164+ # Check for direct prompt override
165+ if [[ -n "${{ inputs.direct_prompt }}" ]]; then
166+ TRIGGER_FOUND="true"
167+ USER_REQUEST="${{ inputs.direct_prompt }}"
168+ CONTEXT_INFO="Automated GitHub workflow"
169+ fi
170+
171+ if [[ "$TRIGGER_FOUND" != "true" ]]; then
172+ echo "❌ No trigger phrase found or direct prompt provided. Exiting gracefully."
173+ echo "SKIP_EXECUTION=true" >> $GITHUB_ENV
174+ exit 0
175+ fi
176+
177+ echo "✅ Trigger found! Context: $CONTEXT_INFO"
178+
179+ # Create comprehensive prompt
180+ mkdir -p /tmp/claude-action
181+ cat > /tmp/claude-action/github-context-prompt.txt << EOF
91182 You are Claude Code, an AI assistant helping with GitHub workflows and code.
92183
93184 Repository: ${{ github.repository }}
94- Context: $CONTEXT
185+ Context: $CONTEXT_INFO
186+ Event: ${{ github.event_name }}
95187
96188 User Request:
97- $PROMPT_TEXT
189+ $USER_REQUEST
190+
191+ Please analyze the request and provide helpful assistance. You have access to repository tools and can help with:
192+ - Code analysis and implementation
193+ - GitHub workflows and automation
194+ - Pull request reviews and feedback
195+ - Issue resolution and bug fixes
196+ - Documentation updates
197+ - Testing and deployment
98198
99- Please analyze the request and provide helpful assistance. You have access to repository tools and can help with code analysis, implementations, and GitHub workflows .
199+ Respond naturally and helpfully to the user's request using the available tools .
100200 EOF
101-
102- PROMPT_PATH="/tmp/claude-action/github-context-prompt.txt"
103- elif [ ! -z "${{ inputs.prompt_file }}" ]; then
104- # Check if the prompt file exists
105- if [ ! -f "${{ inputs.prompt_file }}" ]; then
106- echo "::error::Prompt file '${{ inputs.prompt_file }}' does not exist."
107- exit 1
108- fi
109- PROMPT_PATH="${{ inputs.prompt_file }}"
110- else
111- mkdir -p /tmp/claude-action
112- PROMPT_PATH="/tmp/claude-action/prompt.txt"
113- echo "${{ inputs.prompt }}" > "$PROMPT_PATH"
114- fi
115-
116- # Verify the prompt file is not empty
117- if [ ! -s "$PROMPT_PATH" ]; then
118- echo "::error::Prompt is empty. Please provide a non-empty prompt."
119- exit 1
120- fi
121-
122- # Save the prompt path for the next step
123- echo "PROMPT_PATH=$PROMPT_PATH" >> $GITHUB_ENV
201+
202+ echo "PROMPT_FILE=/tmp/claude-action/github-context-prompt.txt" >> $GITHUB_ENV
203+ echo "SKIP_EXECUTION=false" >> $GITHUB_ENV
124204
125205 - name : Run Claude Code
206+ if : env.SKIP_EXECUTION != 'true'
126207 shell : bash
127- id : run_claude
128208 run : |
129- ALLOWED_TOOLS_ARG=""
130- if [ ! -z "${{ inputs.allowed_tools }}" ]; then
131- ALLOWED_TOOLS_ARG="--allowedTools ${{ inputs.allowed_tools }}"
209+ echo "🚀 Running Claude Code with GitHub context..."
210+
211+ # Build command arguments
212+ CMD_ARGS=("-p" "--verbose" "--output-format" "stream-json")
213+
214+ # Add timeout if specified
215+ if [[ -n "${{ inputs.timeout_minutes }}" ]]; then
216+ CMD_ARGS+=("--timeout" "${{ inputs.timeout_minutes }}m")
132217 fi
133-
134- # Set a timeout to ensure the command doesn't run indefinitely
135- timeout_seconds=$((${{ inputs.timeout_minutes }} * 60))
136-
137- if [ -z "${{ inputs.output_file }}" ]; then
138- # Run Claude Code and output to console
139- timeout $timeout_seconds claude \
140- -p \
141- --verbose \
142- --output-format stream-json \
143- "$(cat ${{ env.PROMPT_PATH }})" \
144- ${{ inputs.allowed_tools != '' && format('--allowedTools "{0}"', inputs.allowed_tools) || '' }}
145- else
146- # Run Claude Code and tee output to console and file
147- timeout $timeout_seconds claude \
148- -p \
149- --verbose \
150- --output-format stream-json \
151- "$(cat ${{ env.PROMPT_PATH }})" \
152- ${{ inputs.allowed_tools != '' && format('--allowedTools "{0}"', inputs.allowed_tools) || '' }} | tee output.txt
153-
154- # Process output.txt into JSON in a separate step
155- jq -s '.' output.txt > output.json
156-
157- # Extract the result from the last item in the array (system message)
158- jq -r '.[-1].result' output.json > "${{ inputs.output_file }}"
159-
160- echo "Complete output saved to output.json, final response saved to ${{ inputs.output_file }}"
218+
219+ # Add max turns if specified
220+ if [[ -n "${{ inputs.max_turns }}" ]]; then
221+ CMD_ARGS+=("--max-turns" "${{ inputs.max_turns }}")
222+ fi
223+
224+ # Add allowed tools (include GitHub tools by default)
225+ TOOLS="mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues,mcp__github__create_comment,Read,Write,Edit,Bash"
226+ if [[ -n "${{ inputs.allowed_tools }}" ]]; then
227+ TOOLS="$TOOLS,${{ inputs.allowed_tools }}"
228+ fi
229+ CMD_ARGS+=("--allowedTools" "$TOOLS")
230+
231+ # Add disallowed tools
232+ if [[ -n "${{ inputs.disallowed_tools }}" ]]; then
233+ CMD_ARGS+=("--disallowedTools" "${{ inputs.disallowed_tools }}")
161234 fi
235+
236+ # Add custom instructions
237+ if [[ -n "${{ inputs.custom_instructions }}" ]]; then
238+ CMD_ARGS+=("--custom-instructions" "${{ inputs.custom_instructions }}")
239+ fi
240+
241+ # Read the prompt content
242+ PROMPT_CONTENT=$(cat "${{ env.PROMPT_FILE }}")
243+
244+ echo "📝 Executing: claude ${CMD_ARGS[*]} \"[prompt content]\""
245+
246+ # Execute Claude Code with timeout
247+ TIMEOUT_SECONDS=$((${{ inputs.timeout_minutes }} * 60))
248+ timeout $TIMEOUT_SECONDS claude "${CMD_ARGS[@]}" "$PROMPT_CONTENT"
249+
250+ echo "✅ Claude Code execution completed"
162251 env :
163252 ANTHROPIC_API_KEY : ${{ inputs.anthropic_api_key }}
164253 GITHUB_TOKEN : ${{ inputs.github_token }}
0 commit comments