From f862b5a16a88dd290268e4adc456ab2487f3cbb0 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 5 Jun 2025 23:19:03 +0000 Subject: [PATCH 01/28] chore: update claude-code-base-action to v0.0.11 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 71594cd67..3c4aafa74 100644 --- a/action.yml +++ b/action.yml @@ -105,7 +105,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@9e4e150978667888ba2108a2ee63a79bf9cfbe06 # v0.0.10 + uses: anthropics/claude-code-base-action@d2fb5ddc682e71cb36b6e9379b601e88cf37a4b7 # v0.0.11 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 4bd9c2053aef2ba7e636657860ce3edcefd40d4e Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 6 Jun 2025 15:30:07 +0000 Subject: [PATCH 02/28] chore: update claude-code-base-action to v0.0.12 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 3c4aafa74..e0be79e14 100644 --- a/action.yml +++ b/action.yml @@ -105,7 +105,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@d2fb5ddc682e71cb36b6e9379b601e88cf37a4b7 # v0.0.11 + uses: anthropics/claude-code-base-action@0cedc118d1f9c17aa8c401d7b3f6f01d0efcc8fa # v0.0.12 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 47ea5c2a699c59955750d39be0a9ba04bbe9dfb9 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 6 Jun 2025 19:44:49 +0000 Subject: [PATCH 03/28] chore: update claude-code-base-action to v0.0.13 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index e0be79e14..0c8414f84 100644 --- a/action.yml +++ b/action.yml @@ -105,7 +105,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@0cedc118d1f9c17aa8c401d7b3f6f01d0efcc8fa # v0.0.12 + uses: anthropics/claude-code-base-action@79b8cfc932eb13806c23905842145e6f05c89e2e # v0.0.13 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 9b50f473cb36959fa9c2eae5bb079d22270adf35 Mon Sep 17 00:00:00 2001 From: Sepehr Sobhani Date: Sun, 8 Jun 2025 16:24:25 -0400 Subject: [PATCH 04/28] Update allowed tools align with what is available in github-mcp-server (#145) --- .github/workflows/claude-review.yml | 2 +- examples/claude-auto-review.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index b87110ea0..0beb47a98 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -29,4 +29,4 @@ jobs: Be constructive and specific in your feedback. Give inline comments where applicable. anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - allowed_tools: "mcp__github__add_pull_request_review_comment" + allowed_tools: "mcp__github__create_pending_pull_request_review,mcp__github__add_pull_request_review_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff" diff --git a/examples/claude-auto-review.yml b/examples/claude-auto-review.yml index bf1bfa510..0b2e0ba4f 100644 --- a/examples/claude-auto-review.yml +++ b/examples/claude-auto-review.yml @@ -35,4 +35,4 @@ jobs: Provide constructive feedback with specific suggestions for improvement. Use inline comments to highlight specific areas of concern. - # allowed_tools: "mcp__github__add_pull_request_review_comment" + # allowed_tools: "mcp__github__create_pending_pull_request_review,mcp__github__add_pull_request_review_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff" From 37483ba1128de6e5b33da71cff57ee65c25a4372 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 9 Jun 2025 13:28:22 -0400 Subject: [PATCH 05/28] feat: add max_turns parameter support (#149) * feat: add max_turns parameter support - Add max_turns input to action.yml with proper description - Pass max_turns parameter through to claude-code-base-action - Update README with documentation and examples for max_turns usage - Add comprehensive tests to verify max_turns configuration - Add yaml dependency for test parsing Closes #148 Co-authored-by: ashwin-ant * chore: remove max-turns test and yaml dependency Co-authored-by: ashwin-ant * chore: revert package.json and bun.lock changes Co-authored-by: ashwin-ant * Update action.yml * prettier --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: ashwin-ant --- README.md | 21 +++++++++++++++++++++ action.yml | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/README.md b/README.md index 4c4a0376b..89d92a75c 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ jobs: # NODE_ENV: test # DEBUG: true # API_URL: https://api.example.com + # Optional: limit the number of conversation turns + # max_turns: "5" ``` ## Inputs @@ -78,6 +80,7 @@ jobs: | --------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | --------- | | `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - | | `direct_prompt` | Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) | No | - | +| `max_turns` | Maximum number of conversation turns Claude can take (limits back-and-forth exchanges) | No | - | | `timeout_minutes` | Timeout in minutes for execution | No | `30` | | `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - | | `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - | @@ -311,6 +314,24 @@ You can pass custom environment variables to Claude Code execution using the `cl The `claude_env` input accepts YAML format where each line defines a key-value pair. These environment variables will be available to Claude Code during execution, allowing it to run tests, build processes, or other commands that depend on specific environment configurations. +### Limiting Conversation Turns + +You can use the `max_turns` parameter to limit the number of back-and-forth exchanges Claude can have during task execution. This is useful for: + +- Controlling costs by preventing runaway conversations +- Setting time boundaries for automated workflows +- Ensuring predictable behavior in CI/CD pipelines + +```yaml +- uses: anthropics/claude-code-action@beta + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + max_turns: "5" # Limit to 5 conversation turns + # ... other inputs +``` + +When the turn limit is reached, Claude will stop execution gracefully. Choose a value that gives Claude enough turns to complete typical tasks while preventing excessive usage. + ### Custom Tools By default, Claude only has access to: diff --git a/action.yml b/action.yml index 0c8414f84..15274c6c0 100644 --- a/action.yml +++ b/action.yml @@ -62,6 +62,10 @@ inputs: 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 @@ -111,6 +115,7 @@ runs: allowed_tools: ${{ env.ALLOWED_TOOLS }} disallowed_tools: ${{ env.DISALLOWED_TOOLS }} timeout_minutes: ${{ inputs.timeout_minutes }} + max_turns: ${{ inputs.max_turns }} model: ${{ inputs.model || inputs.anthropic_model }} mcp_config: ${{ steps.prepare.outputs.mcp_config }} use_bedrock: ${{ inputs.use_bedrock }} From e5b16332494238ba09af60903ea07bbb918db843 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 9 Jun 2025 18:58:08 -0400 Subject: [PATCH 06/28] feat: add roadmap for Claude Code GitHub Action v1.0 (#150) Add ROADMAP.md documenting planned features and improvements for reaching v1.0: - GitHub Action CI results visibility - Cross-repo support - Workflow file modification capabilities - Additional event trigger support - Configurable commit signing - Enhanced code review features - Bot user trigger support - Customizable base prompts The roadmap provides transparency on development priorities and invites community feedback and contributions. --- ROADMAP.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 ROADMAP.md diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 000000000..9bf66c447 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,20 @@ +# Claude Code GitHub Action Roadmap + +Thank you for trying out the beta of our GitHub Action! This document outlines our path to `v1.0`. Items are not necessarily in priority order. + +## Path to 1.0 + +- **Ability to see GitHub Action CI results** - This will enable Claude to look at CI failures and make updates to PRs to fix test failures, lint errors, and the like. +- **Cross-repo support** - Enable Claude to work across multiple repositories in a single session +- **Ability to modify workflow files** - Let Claude update GitHub Actions workflows and other CI configuration files +- **Support for workflow_dispatch and repository_dispatch events** - Dispatch Claude on events triggered via API from other workflows or from other services +- **Ability to disable commit signing** - Option to turn off GPG signing for environments where it's not required. This will enable Claude to use normal `git` bash commands for committing. This will likely become the default behavior once added. +- **Better code review behavior** - Support inline comments on specific lines, provide higher quality reviews with more actionable feedback +- **Support triggering @claude from bot users** - Allow automation and bot accounts to invoke Claude +- **Customizable base prompts** - Full control over Claude's initial context with template variables like `$PR_COMMENTS`, `$PR_FILES`, etc. Users can replace our default prompt entirely while still accessing key contextual data + +--- + +**Note:** This roadmap represents our current vision for reaching `v1.0` and is subject to change based on user feedback and development priorities. + +We welcome feedback on these planned features! If you're interested in contributing to any of these features, please open an issue to discuss implementation details with us. We're also open to suggestions for new features not listed here. From 37ec8e47813bc9d7755f0f56ce7f7f290941299e Mon Sep 17 00:00:00 2001 From: atsushi-ishibashi Date: Tue, 10 Jun 2025 21:59:55 +0900 Subject: [PATCH 07/28] fix: set disallowed_tools as env when runing prepare.ts (#151) --- action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yml b/action.yml index 15274c6c0..1d9b15b38 100644 --- a/action.yml +++ b/action.yml @@ -100,6 +100,7 @@ runs: ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }} BASE_BRANCH: ${{ inputs.base_branch }} ALLOWED_TOOLS: ${{ inputs.allowed_tools }} + DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }} CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }} DIRECT_PROMPT: ${{ inputs.direct_prompt }} MCP_CONFIG: ${{ inputs.mcp_config }} From bdd0c925cb06995712d4dbd690e8b8bc513a08eb Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 10 Jun 2025 19:08:55 +0000 Subject: [PATCH 08/28] chore: update claude-code-base-action to v0.0.14 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 1d9b15b38..97c2d5ae8 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@79b8cfc932eb13806c23905842145e6f05c89e2e # v0.0.13 + uses: anthropics/claude-code-base-action@a0d79f9c1798b06292dbc80f8f95cf742ce7a213 # v0.0.14 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 3bcfbe73859ddf55e4cb2cda805ba8582b5b2237 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Tue, 10 Jun 2025 19:36:52 -0400 Subject: [PATCH 09/28] feat: add MultiEdit to base_allowed_tools (#155) Add MultiEdit tool to the BASE_ALLOWED_TOOLS array to enable Claude Code to use the MultiEdit tool for making multiple edits to a single file in one operation. Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: ashwin-ant --- src/create-prompt/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index 4a9f17b2b..7b332f419 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -24,6 +24,7 @@ export type { CommonFields, PreparedContext } from "./types"; const BASE_ALLOWED_TOOLS = [ "Edit", + "MultiEdit", "Glob", "Grep", "LS", From 25f9b8ef9ec0e3c1882b2075a6f1a14bd2458ab7 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Wed, 11 Jun 2025 17:45:05 -0400 Subject: [PATCH 10/28] fix: add baseUrl to Octokit initialization in update_claude_comment (#157) * fix: add baseUrl to Octokit initialization in update_claude_comment Fixes Bad credentials error on GitHub Enterprise Server by passing GITHUB_API_URL as baseUrl when initializing Octokit, consistent with other Octokit instances in the codebase. Fixes #156 Related to #107 Co-authored-by: ashwin-ant * fix: pass GITHUB_API_URL as env var to MCP server Update the MCP server initialization to pass GITHUB_API_URL as an environment variable, allowing it to work correctly with GitHub Enterprise Server instances. Co-authored-by: ashwin-ant * fix: import GITHUB_API_URL from config in install-mcp-server Use the centralized GITHUB_API_URL constant from src/github/api/config.ts instead of reading directly from process.env when passing environment variables to the MCP server. This ensures consistency with how the API URL is handled throughout the codebase. Co-authored-by: ashwin-ant * fix --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: ashwin-ant --- src/mcp/github-file-ops-server.ts | 1 + src/mcp/install-mcp-server.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/mcp/github-file-ops-server.ts b/src/mcp/github-file-ops-server.ts index a34f11595..9a769af1a 100644 --- a/src/mcp/github-file-ops-server.ts +++ b/src/mcp/github-file-ops-server.ts @@ -466,6 +466,7 @@ server.tool( const octokit = new Octokit({ auth: githubToken, + baseUrl: GITHUB_API_URL, }); const isPullRequestReviewComment = diff --git a/src/mcp/install-mcp-server.ts b/src/mcp/install-mcp-server.ts index 0eba6af54..0fa543621 100644 --- a/src/mcp/install-mcp-server.ts +++ b/src/mcp/install-mcp-server.ts @@ -1,4 +1,5 @@ import * as core from "@actions/core"; +import { GITHUB_API_URL } from "../github/api/config"; type PrepareConfigParams = { githubToken: string; @@ -46,6 +47,7 @@ export async function prepareMcpConfig( ...(claudeCommentId && { CLAUDE_COMMENT_ID: claudeCommentId }), GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME || "", IS_PR: process.env.IS_PR || "false", + GITHUB_API_URL: GITHUB_API_URL, }, }, }, From 56d8eac7ceb280fa26e0b4efcfd4749a6010e0a7 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 11 Jun 2025 22:03:34 +0000 Subject: [PATCH 11/28] chore: update claude-code-base-action to v0.0.17 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 97c2d5ae8..e51883a06 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@a0d79f9c1798b06292dbc80f8f95cf742ce7a213 # v0.0.14 + uses: anthropics/claude-code-base-action@4d2f064606b1c757911a10183c7edb07e99d2dca # v0.0.17 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From b10f287695caa3a755ab23184c63137ab72b7843 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 11 Jun 2025 23:01:51 +0000 Subject: [PATCH 12/28] chore: update claude-code-base-action to v0.0.18 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index e51883a06..00847d13c 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@4d2f064606b1c757911a10183c7edb07e99d2dca # v0.0.17 + uses: anthropics/claude-code-base-action@3933d9c3c25f2b027392a370be6f0bbd5989b271 # v0.0.18 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 55966a1dc07a6c8216dd0d6df53c9a9281f25a26 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 12 Jun 2025 21:55:17 +0000 Subject: [PATCH 13/28] chore: update claude-code-base-action to v0.0.19 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 00847d13c..5e464e0a3 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@3933d9c3c25f2b027392a370be6f0bbd5989b271 # v0.0.18 + uses: anthropics/claude-code-base-action@ebd8558e902b3db132e89863de49565fcb9aec46 # v0.0.19 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 41dd0aa695a06b94f18ce26fd851bfd6ed9d8760 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Thu, 12 Jun 2025 18:16:36 -0400 Subject: [PATCH 14/28] feat: use GitHub display name in Co-authored-by trailers (#163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: use GitHub display name in Co-authored-by trailers - Add name field to GitHubAuthor type - Update GraphQL queries to fetch user display names - Add triggerDisplayName to CommonFields type - Extract display name from fetched GitHub data in prepareContext - Update Co-authored-by trailer generation to use display name when available This ensures consistency with GitHub's web interface behavior where Co-authored-by trailers use the user's display name rather than username. Co-authored-by: ashwin-ant * fix: update GraphQL queries to handle Actor type correctly The name field is only available on the User subtype of Actor in GitHub's GraphQL API. This commit updates the queries to use inline fragments (... on User) to conditionally access the name field when the actor is a User type. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * refactor: clarify Co-authored-by instructions in prompt Replace interpolated values with clear references to XML tags and add explicit formatting instructions. This makes it clearer how to use the GitHub display name when available while maintaining the username for the email portion. Changes: - Use explicit references to and tags - Add clear formatting instructions and example - Explain fallback behavior when display name is not available 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * feat: fetch trigger user display name via dedicated GraphQL query Instead of trying to extract the display name from existing data (which was incomplete due to Actor type limitations), we now: - Add a dedicated USER_QUERY to fetch user display names - Pass the trigger username to fetchGitHubData - Fetch the display name during data collection phase - Simplify prepareContext to use the pre-fetched display name This ensures we always get the correct display name for Co-authored-by trailers, regardless of where the trigger came from. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: ashwin-ant Co-authored-by: Claude --- src/create-prompt/index.ts | 7 +++++-- src/entrypoints/prepare.ts | 1 + src/github/api/queries/github.ts | 8 ++++++++ src/github/data/fetcher.ts | 33 +++++++++++++++++++++++++++++++- src/github/types.ts | 1 + test/create-prompt.test.ts | 2 +- 6 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index 7b332f419..d498cf9b5 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -418,6 +418,7 @@ ${ } ${context.claudeCommentId} ${context.triggerUsername ?? "Unknown"} +${githubData.triggerDisplayName ?? context.triggerUsername ?? "Unknown"} ${context.triggerPhrase} ${ (eventData.eventName === "issue_comment" || @@ -503,12 +504,14 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov ? ` - Push directly using mcp__github_file_ops__commit_files to the existing branch (works for both new and existing files). - Use mcp__github_file_ops__commit_files to commit files atomically in a single commit (supports single or multiple files). - - When pushing changes with this tool and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>" line in the commit message.` + - When pushing changes with this tool and the trigger user is not "Unknown", include a Co-authored-by trailer in the commit message. + - Use: "Co-authored-by: ${githubData.triggerDisplayName ?? context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>"` : ` - You are already on the correct branch (${eventData.claudeBranch || "the PR branch"}). Do not create a new branch. - Push changes directly to the current branch using mcp__github_file_ops__commit_files (works for both new and existing files) - Use mcp__github_file_ops__commit_files to commit files atomically in a single commit (supports single or multiple files). - - When pushing changes and TRIGGER_USERNAME is not "Unknown", include a "Co-authored-by: ${context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>" line in the commit message. + - When pushing changes and the trigger user is not "Unknown", include a Co-authored-by trailer in the commit message. + - Use: "Co-authored-by: ${githubData.triggerDisplayName ?? context.triggerUsername} <${context.triggerUsername}@users.noreply.github.com>" ${ eventData.claudeBranch ? `- Provide a URL to create a PR manually in this format: diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts index 6b240d8ab..f8b5dc2af 100644 --- a/src/entrypoints/prepare.ts +++ b/src/entrypoints/prepare.ts @@ -59,6 +59,7 @@ async function run() { repository: `${context.repository.owner}/${context.repository.repo}`, prNumber: context.entityNumber.toString(), isPR: context.isPR, + triggerUsername: context.actor, }); // Step 8: Setup branch diff --git a/src/github/api/queries/github.ts b/src/github/api/queries/github.ts index 20b5db9c4..e0e4c259d 100644 --- a/src/github/api/queries/github.ts +++ b/src/github/api/queries/github.ts @@ -104,3 +104,11 @@ export const ISSUE_QUERY = ` } } `; + +export const USER_QUERY = ` + query($login: String!) { + user(login: $login) { + name + } + } +`; diff --git a/src/github/data/fetcher.ts b/src/github/data/fetcher.ts index a5b0b0ae3..b1dc26d39 100644 --- a/src/github/data/fetcher.ts +++ b/src/github/data/fetcher.ts @@ -1,6 +1,6 @@ import { execSync } from "child_process"; import type { Octokits } from "../api/client"; -import { ISSUE_QUERY, PR_QUERY } from "../api/queries/github"; +import { ISSUE_QUERY, PR_QUERY, USER_QUERY } from "../api/queries/github"; import type { GitHubComment, GitHubFile, @@ -18,6 +18,7 @@ type FetchDataParams = { repository: string; prNumber: string; isPR: boolean; + triggerUsername?: string; }; export type GitHubFileWithSHA = GitHubFile & { @@ -31,6 +32,7 @@ export type FetchDataResult = { changedFilesWithSHA: GitHubFileWithSHA[]; reviewData: { nodes: GitHubReview[] } | null; imageUrlMap: Map; + triggerDisplayName?: string | null; }; export async function fetchGitHubData({ @@ -38,6 +40,7 @@ export async function fetchGitHubData({ repository, prNumber, isPR, + triggerUsername, }: FetchDataParams): Promise { const [owner, repo] = repository.split("/"); if (!owner || !repo) { @@ -191,6 +194,12 @@ export async function fetchGitHubData({ allComments, ); + // Fetch trigger user display name if username is provided + let triggerDisplayName: string | null | undefined; + if (triggerUsername) { + triggerDisplayName = await fetchUserDisplayName(octokits, triggerUsername); + } + return { contextData, comments, @@ -198,5 +207,27 @@ export async function fetchGitHubData({ changedFilesWithSHA, reviewData, imageUrlMap, + triggerDisplayName, + }; +} + +export type UserQueryResponse = { + user: { + name: string | null; }; +}; + +export async function fetchUserDisplayName( + octokits: Octokits, + login: string, +): Promise { + try { + const result = await octokits.graphql(USER_QUERY, { + login, + }); + return result.user.name; + } catch (error) { + console.warn(`Failed to fetch user display name for ${login}:`, error); + return null; + } } diff --git a/src/github/types.ts b/src/github/types.ts index 28c4aa170..c46c29f8c 100644 --- a/src/github/types.ts +++ b/src/github/types.ts @@ -1,6 +1,7 @@ // Types for GitHub GraphQL query responses export type GitHubAuthor = { login: string; + name?: string; }; export type GitHubComment = { diff --git a/test/create-prompt.test.ts b/test/create-prompt.test.ts index 65c5625c2..472ff65ba 100644 --- a/test/create-prompt.test.ts +++ b/test/create-prompt.test.ts @@ -316,7 +316,7 @@ describe("generatePrompt", () => { expect(prompt).toContain("johndoe"); expect(prompt).toContain( - "Co-authored-by: johndoe ", + 'Use: "Co-authored-by: johndoe "', ); }); From a8d323af27aca1f570b0af8115114dcdf052932a Mon Sep 17 00:00:00 2001 From: Bastian Gutschke Date: Fri, 13 Jun 2025 16:13:30 +0200 Subject: [PATCH 15/28] feat: use dynamic fetch depth based on PR commit count (#169) - Replace fixed depth of 20 with dynamic calculation - Use Math.max(commitCount, 20) to ensure minimum context --- src/github/operations/branch.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/github/operations/branch.ts b/src/github/operations/branch.ts index 337964885..f0b1a959b 100644 --- a/src/github/operations/branch.ts +++ b/src/github/operations/branch.ts @@ -45,9 +45,16 @@ export async function setupBranch( const branchName = prData.headRefName; - // Execute git commands to checkout PR branch (shallow fetch for performance) - // Fetch the branch with a depth of 20 to avoid fetching too much history, while still allowing for some context - await $`git fetch origin --depth=20 ${branchName}`; + // Determine optimal fetch depth based on PR commit count, with a minimum of 20 + const commitCount = prData.commits.totalCount; + const fetchDepth = Math.max(commitCount, 20); + + console.log( + `PR #${entityNumber}: ${commitCount} commits, using fetch depth ${fetchDepth}`, + ); + + // Execute git commands to checkout PR branch (dynamic depth based on PR size) + await $`git fetch origin --depth=${fetchDepth} ${branchName}`; await $`git checkout ${branchName}`; console.log(`Successfully checked out PR branch for PR #${entityNumber}`); From 67d7753c800205a88ae49f0e43d063febb88e5ff Mon Sep 17 00:00:00 2001 From: Hidetake Iwata Date: Fri, 13 Jun 2025 23:19:36 +0900 Subject: [PATCH 16/28] Accept multiline input for allowed_tools and disallowed_tools (#168) --- README.md | 11 +++++-- src/github/context.ts | 18 ++++++------ test/github/context.test.ts | 57 +++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 test/github/context.test.ts diff --git a/README.md b/README.md index 89d92a75c..dddbe6f9d 100644 --- a/README.md +++ b/README.md @@ -347,8 +347,15 @@ Claude does **not** have access to execute arbitrary Bash commands by default. I ```yaml - uses: anthropics/claude-code-action@beta with: - allowed_tools: "Bash(npm install),Bash(npm run test),Edit,Replace,NotebookEditCell" - disallowed_tools: "TaskOutput,KillTask" + allowed_tools: | + Bash(npm install) + Bash(npm run test) + Edit + Replace + NotebookEditCell + disallowed_tools: | + TaskOutput + KillTask # ... other inputs ``` diff --git a/src/github/context.ts b/src/github/context.ts index 1e193038b..d8b158105 100644 --- a/src/github/context.ts +++ b/src/github/context.ts @@ -52,14 +52,8 @@ export function parseGitHubContext(): ParsedGitHubContext { inputs: { triggerPhrase: process.env.TRIGGER_PHRASE ?? "@claude", assigneeTrigger: process.env.ASSIGNEE_TRIGGER ?? "", - allowedTools: (process.env.ALLOWED_TOOLS ?? "") - .split(",") - .map((tool) => tool.trim()) - .filter((tool) => tool.length > 0), - disallowedTools: (process.env.DISALLOWED_TOOLS ?? "") - .split(",") - .map((tool) => tool.trim()) - .filter((tool) => tool.length > 0), + allowedTools: parseMultilineInput(process.env.ALLOWED_TOOLS ?? ""), + disallowedTools: parseMultilineInput(process.env.DISALLOWED_TOOLS ?? ""), customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "", directPrompt: process.env.DIRECT_PROMPT ?? "", baseBranch: process.env.BASE_BRANCH, @@ -116,6 +110,14 @@ export function parseGitHubContext(): ParsedGitHubContext { } } +export function parseMultilineInput(s: string): string[] { + return s + .split(/,|[\n\r]+/) + .map((tool) => tool.replace(/#.+$/, "")) + .map((tool) => tool.trim()) + .filter((tool) => tool.length > 0); +} + export function isIssuesEvent( context: ParsedGitHubContext, ): context is ParsedGitHubContext & { payload: IssuesEvent } { diff --git a/test/github/context.test.ts b/test/github/context.test.ts new file mode 100644 index 000000000..bfdf0265d --- /dev/null +++ b/test/github/context.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from "bun:test"; +import { parseMultilineInput } from "../../src/github/context"; + +describe("parseMultilineInput", () => { + it("should parse a comma-separated string", () => { + const input = `Bash(bun install),Bash(bun test:*),Bash(bun typecheck)`; + const result = parseMultilineInput(input); + expect(result).toEqual([ + "Bash(bun install)", + "Bash(bun test:*)", + "Bash(bun typecheck)", + ]); + }); + + it("should parse multiline string", () => { + const input = `Bash(bun install) +Bash(bun test:*) +Bash(bun typecheck)`; + const result = parseMultilineInput(input); + expect(result).toEqual([ + "Bash(bun install)", + "Bash(bun test:*)", + "Bash(bun typecheck)", + ]); + }); + + it("should parse comma-separated multiline line", () => { + const input = `Bash(bun install),Bash(bun test:*) +Bash(bun typecheck)`; + const result = parseMultilineInput(input); + expect(result).toEqual([ + "Bash(bun install)", + "Bash(bun test:*)", + "Bash(bun typecheck)", + ]); + }); + + it("should ignore comments", () => { + const input = `Bash(bun install), +Bash(bun test:*) # For testing +# For type checking +Bash(bun typecheck) +`; + const result = parseMultilineInput(input); + expect(result).toEqual([ + "Bash(bun install)", + "Bash(bun test:*)", + "Bash(bun typecheck)", + ]); + }); + + it("should parse an empty string", () => { + const input = ""; + const result = parseMultilineInput(input); + expect(result).toEqual([]); + }); +}); From def1b3a94ee489d17f4959f366dd44e1434da02a Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Fri, 13 Jun 2025 17:15:50 -0400 Subject: [PATCH 17/28] docs: add uv example for Python MCP servers in mcp_config section (#170) Added documentation showing how to configure Python-based MCP servers using uv with the --directory argument, as requested in issue #130. Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ashwin Bhat --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index dddbe6f9d..0dceb8cd0 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,40 @@ For MCP servers that require sensitive information like API keys or tokens, use # ... other inputs ``` +#### Using Python MCP Servers with uv + +For Python-based MCP servers managed with `uv`, you need to specify the directory containing your server: + +```yaml +- uses: anthropics/claude-code-action@beta + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + mcp_config: | + { + "mcpServers": { + "my-python-server": { + "type": "stdio", + "command": "uv", + "args": [ + "--directory", + "${{ github.workspace }}/path/to/server/", + "run", + "server_file.py" + ] + } + } + } + allowed_tools: "my-python-server__" # Replace with your server's tool names + # ... other inputs +``` + +For example, if your Python MCP server is at `mcp_servers/weather.py`, you would use: + +```yaml +"args": + ["--directory", "${{ github.workspace }}/mcp_servers/", "run", "weather.py"] +``` + **Important**: - Always use GitHub Secrets (`${{ secrets.SECRET_NAME }}`) for sensitive values like API keys, tokens, or passwords. Never hardcode secrets directly in the workflow file. From ffb2927088ee8d2e3fab39463c9742d64c4ebefc Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Fri, 13 Jun 2025 17:43:56 -0400 Subject: [PATCH 18/28] feat: add release workflow with beta tag management (#171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auto-increment patch version for new releases - Update beta tag to point to latest release - Update major version tag (v0) for simplified action usage - Support dry run mode for testing - Keep beta as the "latest" release channel 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- .github/workflows/release.yml | 138 ++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..97d9652d3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,138 @@ +name: Create Release + +on: + workflow_dispatch: + inputs: + dry_run: + description: "Dry run (only show what would be created)" + required: false + type: boolean + default: false + +jobs: + create-release: + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + next_version: ${{ steps.next_version.outputs.next_version }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get latest tag + id: get_latest_tag + run: | + # Get only version tags (v + number pattern) + latest_tag=$(git tag -l 'v[0-9]*' | sort -V | tail -1 || echo "v0.0.0") + if [ -z "$latest_tag" ]; then + latest_tag="v0.0.0" + fi + echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT + echo "Latest tag: $latest_tag" + + - name: Calculate next version + id: next_version + run: | + latest_tag="${{ steps.get_latest_tag.outputs.latest_tag }}" + # Remove 'v' prefix and split by dots + version=${latest_tag#v} + IFS='.' read -ra VERSION_PARTS <<< "$version" + + # Increment patch version + major=${VERSION_PARTS[0]:-0} + minor=${VERSION_PARTS[1]:-0} + patch=${VERSION_PARTS[2]:-0} + patch=$((patch + 1)) + + next_version="v${major}.${minor}.${patch}" + echo "next_version=$next_version" >> $GITHUB_OUTPUT + echo "Next version: $next_version" + + - name: Display dry run info + if: ${{ inputs.dry_run }} + run: | + echo "🔍 DRY RUN MODE" + echo "Would create tag: ${{ steps.next_version.outputs.next_version }}" + echo "From commit: ${{ github.sha }}" + echo "Previous tag: ${{ steps.get_latest_tag.outputs.latest_tag }}" + + - name: Create and push tag + if: ${{ !inputs.dry_run }} + run: | + next_version="${{ steps.next_version.outputs.next_version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git tag -a "$next_version" -m "Release $next_version" + git push origin "$next_version" + + - name: Create Release + if: ${{ !inputs.dry_run }} + env: + GH_TOKEN: ${{ github.token }} + run: | + next_version="${{ steps.next_version.outputs.next_version }}" + + gh release create "$next_version" \ + --title "$next_version" \ + --generate-notes \ + --latest=false # We want to keep beta as the latest + + update-beta-tag: + needs: create-release + if: ${{ !inputs.dry_run }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Update beta tag + run: | + # Get the latest version tag + VERSION=$(git tag -l 'v[0-9]*' | sort -V | tail -1) + + # Update the beta tag to point to this release + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -fa beta -m "Update beta tag to ${VERSION}" + git push origin beta --force + + - name: Update beta release to be latest + env: + GH_TOKEN: ${{ github.token }} + run: | + # Update beta release to be marked as latest + gh release edit beta --latest + + update-major-tag: + needs: create-release + if: ${{ !inputs.dry_run }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Update major version tag + run: | + next_version="${{ needs.create-release.outputs.next_version }}" + # Extract major version (e.g., v0 from v0.0.20) + major_version=$(echo "$next_version" | cut -d. -f1) + + # Update the major version tag to point to this release + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -fa "$major_version" -m "Update $major_version tag to $next_version" + git push origin "$major_version" --force + + echo "Updated $major_version tag to point to $next_version" From 3c748dc92755ad477a2b50e53016af5cd29d1776 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 14 Jun 2025 02:45:07 +0000 Subject: [PATCH 19/28] chore: update claude-code-base-action to v0.0.20 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 5e464e0a3..697ea8b30 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@ebd8558e902b3db132e89863de49565fcb9aec46 # v0.0.19 + uses: anthropics/claude-code-base-action@f481f924b73a7085d9efea0e50a3ba171ed1d74b # v0.0.20 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From e0d3fec39f30dc4ed990b3ada4cbed665ce797a0 Mon Sep 17 00:00:00 2001 From: Tomohiro Ishibashi <103555868+tomoish@users.noreply.github.com> Date: Mon, 16 Jun 2025 23:40:13 +0900 Subject: [PATCH 20/28] update MCP server image to version 0.5.0 (#175) --- .github/workflows/issue-triage.yml | 2 +- src/mcp/install-mcp-server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 4eb7fd54f..7d821a287 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -32,7 +32,7 @@ jobs: "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:sha-7aced2b" + "ghcr.io/github/github-mcp-server:sha-6d69797" ], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}" diff --git a/src/mcp/install-mcp-server.ts b/src/mcp/install-mcp-server.ts index 0fa543621..8748f67d7 100644 --- a/src/mcp/install-mcp-server.ts +++ b/src/mcp/install-mcp-server.ts @@ -62,7 +62,7 @@ export async function prepareMcpConfig( "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server:sha-e9f748f", // https://github.com/github/github-mcp-server/releases/tag/v0.4.0 + "ghcr.io/github/github-mcp-server:sha-6d69797", // https://github.com/github/github-mcp-server/releases/tag/v0.5.0 ], env: { GITHUB_PERSONAL_ACCESS_TOKEN: githubToken, From 1b94b9e5a85d066d540e74f2b5f616919a874336 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 16 Jun 2025 15:31:43 -0700 Subject: [PATCH 21/28] feat: enhance error reporting with specific error types from Claude execution (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: enhance error reporting with specific error types from Claude execution - Extract error subtypes (error_during_execution, error_max_turns) from result object - Display specific error messages in comment header based on error type - Use total_cost_usd field from SDKResultMessage type - Prevent showing redundant error details when already displayed in header 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * chore: update claude-code-base-action to v0.0.19 * feat: use GitHub display name in Co-authored-by trailers (#163) * feat: use GitHub display name in Co-authored-by trailers - Add name field to GitHubAuthor type - Update GraphQL queries to fetch user display names - Add triggerDisplayName to CommonFields type - Extract display name from fetched GitHub data in prepareContext - Update Co-authored-by trailer generation to use display name when available This ensures consistency with GitHub's web interface behavior where Co-authored-by trailers use the user's display name rather than username. Co-authored-by: ashwin-ant * fix: update GraphQL queries to handle Actor type correctly The name field is only available on the User subtype of Actor in GitHub's GraphQL API. This commit updates the queries to use inline fragments (... on User) to conditionally access the name field when the actor is a User type. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * refactor: clarify Co-authored-by instructions in prompt Replace interpolated values with clear references to XML tags and add explicit formatting instructions. This makes it clearer how to use the GitHub display name when available while maintaining the username for the email portion. Changes: - Use explicit references to and tags - Add clear formatting instructions and example - Explain fallback behavior when display name is not available 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * feat: fetch trigger user display name via dedicated GraphQL query Instead of trying to extract the display name from existing data (which was incomplete due to Actor type limitations), we now: - Add a dedicated USER_QUERY to fetch user display names - Pass the trigger username to fetchGitHubData - Fetch the display name during data collection phase - Simplify prepareContext to use the pre-fetched display name This ensures we always get the correct display name for Co-authored-by trailers, regardless of where the trigger came from. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: ashwin-ant Co-authored-by: Claude * feat: use dynamic fetch depth based on PR commit count (#169) - Replace fixed depth of 20 with dynamic calculation - Use Math.max(commitCount, 20) to ensure minimum context * Accept multiline input for allowed_tools and disallowed_tools (#168) * docs: add uv example for Python MCP servers in mcp_config section (#170) Added documentation showing how to configure Python-based MCP servers using uv with the --directory argument, as requested in issue #130. Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ashwin Bhat * feat: add release workflow with beta tag management (#171) - Auto-increment patch version for new releases - Update beta tag to point to latest release - Update major version tag (v0) for simplified action usage - Support dry run mode for testing - Keep beta as the "latest" release channel 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude * chore: update claude-code-base-action to v0.0.20 * update MCP server image to version 0.5.0 (#175) * refactor: convert error subtype check to switch case Replace if-else chain with switch statement for better readability and maintainability when handling error subtypes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: Claude Co-authored-by: GitHub Actions Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: ashwin-ant Co-authored-by: Bastian Gutschke Co-authored-by: Hidetake Iwata Co-authored-by: Tomohiro Ishibashi <103555868+tomoish@users.noreply.github.com> --- src/entrypoints/update-comment-link.ts | 36 +++++++++++++++++++------- src/github/operations/comment-logic.ts | 19 ++++++++++++-- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts index 9090373e2..c29edf4fd 100644 --- a/src/entrypoints/update-comment-link.ts +++ b/src/entrypoints/update-comment-link.ts @@ -147,6 +147,7 @@ async function run() { } | null = null; let actionFailed = false; let errorDetails: string | undefined; + let errorSubtype: string | undefined; // First check if prepare step failed const prepareSuccess = process.env.PREPARE_SUCCESS !== "false"; @@ -166,23 +167,40 @@ async function run() { // Output file is an array, get the last element which contains execution details if (Array.isArray(outputData) && outputData.length > 0) { const lastElement = outputData[outputData.length - 1]; - if ( - lastElement.type === "result" && - "cost_usd" in lastElement && - "duration_ms" in lastElement - ) { + if (lastElement.type === "result") { + // Extract execution details executionDetails = { - cost_usd: lastElement.cost_usd, + cost_usd: lastElement.total_cost_usd, duration_ms: lastElement.duration_ms, duration_api_ms: lastElement.duration_api_ms, }; + + // Check if this is an error result based on subtype + switch (lastElement.subtype) { + case "error_during_execution": + errorSubtype = "Error during execution"; + // Override the actionFailed flag based on the result + actionFailed = true; + break; + case "error_max_turns": + errorSubtype = "Maximum turns exceeded"; + actionFailed = true; + break; + } } } } - // Check if the Claude action failed - const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false"; - actionFailed = !claudeSuccess; + // Check if the Claude action failed (only if not already determined from result) + if (!actionFailed) { + const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false"; + actionFailed = !claudeSuccess; + } + + // Use errorSubtype as errorDetails if no other error details are available + if (actionFailed && !errorDetails && errorSubtype) { + errorDetails = errorSubtype; + } } catch (error) { console.error("Error reading output file:", error); // If we can't read the file, check for any failure markers diff --git a/src/github/operations/comment-logic.ts b/src/github/operations/comment-logic.ts index 6a4551a6c..95a612e16 100644 --- a/src/github/operations/comment-logic.ts +++ b/src/github/operations/comment-logic.ts @@ -114,6 +114,16 @@ export function updateCommentBody(input: CommentUpdateInput): string { if (actionFailed) { header = "**Claude encountered an error"; + + // Add error type to header if available + if (errorDetails) { + if (errorDetails === "Error during execution") { + header = "**Claude encountered an error during execution"; + } else if (errorDetails === "Maximum turns exceeded") { + header = "**Claude exceeded the maximum number of turns"; + } + } + if (durationStr) { header += ` after ${durationStr}`; } @@ -181,8 +191,13 @@ export function updateCommentBody(input: CommentUpdateInput): string { // Build the new body with blank line between header and separator let newBody = `${header}${links}`; - // Add error details if available - if (actionFailed && errorDetails) { + // Add error details if available (but not if it's just the error type we already showed in header) + if ( + actionFailed && + errorDetails && + errorDetails !== "Error during execution" && + errorDetails !== "Maximum turns exceeded" + ) { newBody += `\n\n\`\`\`\n${errorDetails}\n\`\`\``; } From 2dab3f2afee9c20892ce738654cc68178c1e0e3c Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 16 Jun 2025 16:52:07 -0700 Subject: [PATCH 22/28] =?UTF-8?q?Revert=20"feat:=20enhance=20error=20repor?= =?UTF-8?q?ting=20with=20specific=20error=20types=20from=20Claude=20e?= =?UTF-8?q?=E2=80=A6"=20(#179)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1b94b9e5a85d066d540e74f2b5f616919a874336. --- src/entrypoints/update-comment-link.ts | 36 +++++++------------------- src/github/operations/comment-logic.ts | 19 ++------------ 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts index c29edf4fd..9090373e2 100644 --- a/src/entrypoints/update-comment-link.ts +++ b/src/entrypoints/update-comment-link.ts @@ -147,7 +147,6 @@ async function run() { } | null = null; let actionFailed = false; let errorDetails: string | undefined; - let errorSubtype: string | undefined; // First check if prepare step failed const prepareSuccess = process.env.PREPARE_SUCCESS !== "false"; @@ -167,40 +166,23 @@ async function run() { // Output file is an array, get the last element which contains execution details if (Array.isArray(outputData) && outputData.length > 0) { const lastElement = outputData[outputData.length - 1]; - if (lastElement.type === "result") { - // Extract execution details + if ( + lastElement.type === "result" && + "cost_usd" in lastElement && + "duration_ms" in lastElement + ) { executionDetails = { - cost_usd: lastElement.total_cost_usd, + cost_usd: lastElement.cost_usd, duration_ms: lastElement.duration_ms, duration_api_ms: lastElement.duration_api_ms, }; - - // Check if this is an error result based on subtype - switch (lastElement.subtype) { - case "error_during_execution": - errorSubtype = "Error during execution"; - // Override the actionFailed flag based on the result - actionFailed = true; - break; - case "error_max_turns": - errorSubtype = "Maximum turns exceeded"; - actionFailed = true; - break; - } } } } - // Check if the Claude action failed (only if not already determined from result) - if (!actionFailed) { - const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false"; - actionFailed = !claudeSuccess; - } - - // Use errorSubtype as errorDetails if no other error details are available - if (actionFailed && !errorDetails && errorSubtype) { - errorDetails = errorSubtype; - } + // Check if the Claude action failed + const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false"; + actionFailed = !claudeSuccess; } catch (error) { console.error("Error reading output file:", error); // If we can't read the file, check for any failure markers diff --git a/src/github/operations/comment-logic.ts b/src/github/operations/comment-logic.ts index 95a612e16..6a4551a6c 100644 --- a/src/github/operations/comment-logic.ts +++ b/src/github/operations/comment-logic.ts @@ -114,16 +114,6 @@ export function updateCommentBody(input: CommentUpdateInput): string { if (actionFailed) { header = "**Claude encountered an error"; - - // Add error type to header if available - if (errorDetails) { - if (errorDetails === "Error during execution") { - header = "**Claude encountered an error during execution"; - } else if (errorDetails === "Maximum turns exceeded") { - header = "**Claude exceeded the maximum number of turns"; - } - } - if (durationStr) { header += ` after ${durationStr}`; } @@ -191,13 +181,8 @@ export function updateCommentBody(input: CommentUpdateInput): string { // Build the new body with blank line between header and separator let newBody = `${header}${links}`; - // Add error details if available (but not if it's just the error type we already showed in header) - if ( - actionFailed && - errorDetails && - errorDetails !== "Error during execution" && - errorDetails !== "Maximum turns exceeded" - ) { + // Add error details if available + if (actionFailed && errorDetails) { newBody += `\n\n\`\`\`\n${errorDetails}\n\`\`\``; } From bcf2fe94f89c58fa167a7bf50fe21389f235ec04 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 17 Jun 2025 13:39:54 +0000 Subject: [PATCH 23/28] chore: update claude-code-base-action to v0.0.21 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 697ea8b30..6e7e3e97e 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@f481f924b73a7085d9efea0e50a3ba171ed1d74b # v0.0.20 + uses: anthropics/claude-code-base-action@cef27f3f006b4c6e8394105604f63f20e84ae300 # v0.0.21 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 13ccdab2f8b45fde1825caf9a88c1901f38c92c5 Mon Sep 17 00:00:00 2001 From: Kuma Taro Date: Wed, 18 Jun 2025 02:06:06 +0900 Subject: [PATCH 24/28] fix: correct assignee trigger test to handle different assignee properly (#178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: use direct assignee field * fix: correct assignee trigger test to handle different assignee properly The test was failing because the mockIssueAssignedContext was missing the top-level assignee field that the trigger validation logic checks. Added the missing assignee field to the mock context and updated the test to properly override both the top-level assignee and issue.assignee fields when testing assignment to a different user. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Adjust IssuesAssignedEvent import position (#2) --------- Co-authored-by: Claude --- src/github/context.ts | 7 +++++++ src/github/validation/trigger.ts | 5 +++-- test/mockContext.ts | 6 ++++++ test/trigger-validation.test.ts | 5 +++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/github/context.ts b/src/github/context.ts index d8b158105..f0e81b598 100644 --- a/src/github/context.ts +++ b/src/github/context.ts @@ -1,6 +1,7 @@ import * as github from "@actions/github"; import type { IssuesEvent, + IssuesAssignedEvent, IssueCommentEvent, PullRequestEvent, PullRequestReviewEvent, @@ -147,3 +148,9 @@ export function isPullRequestReviewCommentEvent( ): context is ParsedGitHubContext & { payload: PullRequestReviewCommentEvent } { return context.eventName === "pull_request_review_comment"; } + +export function isIssuesAssignedEvent( + context: ParsedGitHubContext, +): context is ParsedGitHubContext & { payload: IssuesAssignedEvent } { + return isIssuesEvent(context) && context.eventAction === "assigned"; +} diff --git a/src/github/validation/trigger.ts b/src/github/validation/trigger.ts index 6a06153a8..40ee933fc 100644 --- a/src/github/validation/trigger.ts +++ b/src/github/validation/trigger.ts @@ -3,6 +3,7 @@ import * as core from "@actions/core"; import { isIssuesEvent, + isIssuesAssignedEvent, isIssueCommentEvent, isPullRequestEvent, isPullRequestReviewEvent, @@ -22,10 +23,10 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean { } // Check for assignee trigger - if (isIssuesEvent(context) && context.eventAction === "assigned") { + if (isIssuesAssignedEvent(context)) { // Remove @ symbol from assignee_trigger if present let triggerUser = assigneeTrigger.replace(/^@/, ""); - const assigneeUsername = context.payload.issue.assignee?.login || ""; + const assigneeUsername = context.payload.assignee?.login || ""; if (triggerUser && assigneeUsername === triggerUser) { console.log(`Issue assigned to trigger user '${triggerUser}'`); diff --git a/test/mockContext.ts b/test/mockContext.ts index 692137cc9..65250c138 100644 --- a/test/mockContext.ts +++ b/test/mockContext.ts @@ -91,6 +91,12 @@ export const mockIssueAssignedContext: ParsedGitHubContext = { actor: "admin-user", payload: { action: "assigned", + assignee: { + login: "claude-bot", + id: 11111, + avatar_url: "https://avatars.githubusercontent.com/u/11111", + html_url: "https://github.com/claude-bot", + }, issue: { number: 123, title: "Feature: Add dark mode support", diff --git a/test/trigger-validation.test.ts b/test/trigger-validation.test.ts index bbe40bde5..6c368b07e 100644 --- a/test/trigger-validation.test.ts +++ b/test/trigger-validation.test.ts @@ -87,6 +87,11 @@ describe("checkContainsTrigger", () => { ...mockIssueAssignedContext, payload: { ...mockIssueAssignedContext.payload, + assignee: { + ...(mockIssueAssignedContext.payload as IssuesAssignedEvent) + .assignee, + login: "otherUser", + }, issue: { ...(mockIssueAssignedContext.payload as IssuesAssignedEvent).issue, assignee: { From 3486c33ebfa03d71c98e72621759471c45388443 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 17 Jun 2025 21:59:57 +0000 Subject: [PATCH 25/28] chore: update claude-code-base-action to v0.0.22 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 6e7e3e97e..669d2a095 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@cef27f3f006b4c6e8394105604f63f20e84ae300 # v0.0.21 + uses: anthropics/claude-code-base-action@bb2ef1d9768b9e94083d377778120f8f27958a72 # v0.0.22 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 91f620f8c24a9a3d3dbd1b60a4d67cecc13df0ce Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Thu, 19 Jun 2025 07:52:42 -0700 Subject: [PATCH 26/28] docs: remove references to non-existent test-local.sh script (#187) All tests for this repo can be run with `bun test` - the test-local.sh script was a holdover from the base action repo. Fixes #172 Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ashwin Bhat --- CONTRIBUTING.md | 22 +------ example-dispatch-workflow.yml | 73 +++++++++++++++++++++ pr-summary.md | 118 ++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 21 deletions(-) create mode 100644 example-dispatch-workflow.yml create mode 100644 pr-summary.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96824d164..74e61409f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,20 +50,6 @@ Thank you for your interest in contributing to Claude Code Action! This document bun test ``` -2. **Integration Tests** (using GitHub Actions locally): - - ```bash - ./test-local.sh - ``` - - This script: - - - Installs `act` if not present (requires Homebrew on macOS) - - Runs the GitHub Action workflow locally using Docker - - Requires your `ANTHROPIC_API_KEY` to be set - - On Apple Silicon Macs, the script automatically adds the `--container-architecture linux/amd64` flag to avoid compatibility issues. - ## Pull Request Process 1. Create a new branch from `main`: @@ -103,13 +89,7 @@ Thank you for your interest in contributing to Claude Code Action! This document When modifying the action: -1. Test locally with the test script: - - ```bash - ./test-local.sh - ``` - -2. Test in a real GitHub Actions workflow by: +1. Test in a real GitHub Actions workflow by: - Creating a test repository - Using your branch as the action source: ```yaml diff --git a/example-dispatch-workflow.yml b/example-dispatch-workflow.yml new file mode 100644 index 000000000..74cd95def --- /dev/null +++ b/example-dispatch-workflow.yml @@ -0,0 +1,73 @@ +name: Claude Task Executor + +on: + repository_dispatch: + types: [claude-task] + +permissions: + contents: write + pull-requests: write + issues: write + id-token: write # Required for OIDC authentication + +jobs: + execute-claude-task: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Execute Claude Task + uses: anthropics/claude-code-action@main + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + # Base branch for creating task branches + base_branch: main + # Optional: Custom instructions for Claude + custom_instructions: | + Follow the CLAUDE.md guidelines strictly. + Commit changes with descriptive messages. + # Optional: Tool restrictions + allowed_tools: | + file_editor + bash_command + github_comment + mcp__github__create_or_update_file + # Optional: Anthropic API configuration + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + # Or use AWS Bedrock + # aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} + # aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # aws_region: us-east-1 + # Or use Google Vertex AI + # google_credentials: ${{ secrets.GOOGLE_CREDENTIALS }} + # vertex_project: my-project + # vertex_location: us-central1 +# Example: Triggering this workflow from another service +# +# curl -X POST \ +# https://api.github.com/repos/owner/repo/dispatches \ +# -H "Authorization: token $GITHUB_TOKEN" \ +# -H "Accept: application/vnd.github.v3+json" \ +# -d '{ +# "event_type": "claude-task", +# "client_payload": { +# "description": "Analyze the codebase and create a comprehensive test suite for the authentication module", +# "progress_endpoint": "https://api.example.com/claude/progress", +# "correlation_id": "task-auth-tests-2024-01-17" +# } +# }' +# +# The progress_endpoint will receive POST requests with: +# { +# "repository": "owner/repo", +# "run_id": "123456789", +# "correlation_id": "task-auth-tests-2024-01-17", +# "status": "in_progress" | "completed" | "failed", +# "message": "Current progress description", +# "completed_tasks": ["task1", "task2"], +# "current_task": "Working on task3", +# "timestamp": "2024-01-17T12:00:00Z" +# } +# +# Authentication: Progress updates include a GitHub OIDC token in the Authorization header diff --git a/pr-summary.md b/pr-summary.md new file mode 100644 index 000000000..08306497f --- /dev/null +++ b/pr-summary.md @@ -0,0 +1,118 @@ +## Summary + +Adds support for `repository_dispatch` events, enabling backend services to programmatically trigger Claude to perform tasks and receive progress updates via API. + +## Architecture + +```mermaid +sequenceDiagram + participant Backend as Backend Service + participant GH as GitHub + participant Action as Claude Action + participant Claude as Claude + participant MCP as Progress MCP Server + participant API as Progress API + + Backend->>GH: POST /repos/{owner}/{repo}/dispatches + Note over Backend,GH: Payload includes:
- description (task)
- progress_endpoint
- correlation_id + + GH->>Action: Trigger workflow
(repository_dispatch) + + Action->>Action: Parse dispatch payload + Note over Action: Extract task description,
endpoint, correlation_id + + Action->>MCP: Install Progress Server + Note over MCP: Configure with:
- PROGRESS_ENDPOINT
- CORRELATION_ID
- GITHUB_RUN_ID + + Action->>Claude: Execute task with
MCP tools available + + loop Task Execution + Claude->>MCP: update_claude_progress() + MCP->>MCP: Get OIDC token + MCP->>API: POST progress update + Note over API: Payload includes:
- correlation_id
- status
- message
- completed_tasks + API->>Backend: Forward update + end + + Claude->>Action: Task complete + Action->>GH: Commit changes +``` + +## Key Features + +### 1. Repository Dispatch Support + +- New event handler for `repository_dispatch` events +- Extracts task description, progress endpoint, and correlation ID from `client_payload` +- Bypasses GitHub UI interaction for fully programmatic operation + +### 2. Progress Reporting MCP Server + +- New MCP server (`progress-server.ts`) for sending progress updates +- OIDC authentication for secure API communication +- Includes correlation ID in all updates for request tracking + +### 3. Simplified Dispatch Prompts + +- Focused instructions for dispatch events (no PR/issue context) +- Clear directives: answer questions or implement changes +- Automatic progress updates at start and completion + +## Implementation Details + +### Triggering a Dispatch + +```bash +curl -X POST \ + https://api.github.com/repos/{owner}/{repo}/dispatches \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + -d '{ + "event_type": "claude-task", + "client_payload": { + "description": "Implement a new feature that...", + "progress_endpoint": "https://api.example.com/progress", + "correlation_id": "req-123-abc" + } + }' +``` + +### Progress Update Payload + +```json +{ + "repository": "owner/repo", + "run_id": "123456789", + "correlation_id": "req-123-abc", + "status": "in_progress", + "message": "Implementing feature...", + "completed_tasks": ["Setup environment", "Created base structure"], + "current_task": "Writing tests", + "timestamp": "2024-01-17T12:00:00Z" +} +``` + +## Security + +- **OIDC Authentication**: All progress updates use GitHub OIDC tokens +- **Correlation IDs**: Included in request body (not URL) for security +- **Endpoint Validation**: Progress endpoint must be explicitly provided +- **No Credential Storage**: Tokens are generated per-request + +## Testing + +To test the repository_dispatch flow: + +1. Configure workflow with `repository_dispatch` trigger +2. Send dispatch event with required payload +3. Monitor GitHub Actions logs for execution +4. Verify progress updates at configured endpoint + +## Changes + +- Added `repository_dispatch` event handling in `context.ts` +- Created new `progress-server.ts` MCP server +- Updated `isDispatch` flag across all event types +- Modified prompt generation for dispatch events +- Made `githubData` optional for dispatch workflows +- Added correlation ID support throughout the pipeline From 237de9d3299f5e6ec4c97fe3a57ea0fb8f658c09 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 20 Jun 2025 15:38:21 +0000 Subject: [PATCH 27/28] chore: update claude-code-base-action to v0.0.23 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 669d2a095..e662271f0 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@bb2ef1d9768b9e94083d377778120f8f27958a72 # v0.0.22 + uses: anthropics/claude-code-base-action@56355f77b19f27378aaf141b9b7e08cc43b542f6 # v0.0.23 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From ebbd9e9be4686249a2952e1a558bbaba07524380 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 20 Jun 2025 21:50:00 +0000 Subject: [PATCH 28/28] chore: update claude-code-base-action to v0.0.24 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index e662271f0..6c459174a 100644 --- a/action.yml +++ b/action.yml @@ -110,7 +110,7 @@ runs: - name: Run Claude Code id: claude-code if: steps.prepare.outputs.contains_trigger == 'true' - uses: anthropics/claude-code-base-action@56355f77b19f27378aaf141b9b7e08cc43b542f6 # v0.0.23 + uses: anthropics/claude-code-base-action@f382bd1ea00f26043eb461ebabebe0d850572a71 # v0.0.24 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }}