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 01/12] 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 4eb7fd5..7d821a2 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 0fa5436..8748f67 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 02/12] 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 9090373..c29edf4 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 6a4551a..95a612e 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 03/12] =?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 c29edf4..9090373 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 95a612e..6a4551a 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 04/12] 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 697ea8b..6e7e3e9 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 05/12] 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 d8b1581..f0e81b5 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 6a06153..40ee933 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 692137c..65250c1 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 bbe40bd..6c368b0 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 06/12] 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 6e7e3e9..669d2a0 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 07/12] 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 96824d1..74e6140 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 0000000..74cd95d --- /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 0000000..0830649 --- /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 08/12] 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 669d2a0..e662271 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 09/12] 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 e662271..6c45917 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 }} From 28aaa5404d898068f770be4cdd4269c8fb4e18da Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 24 Jun 2025 00:35:11 +0000 Subject: [PATCH 10/12] chore: update claude-code-base-action to v0.0.25 --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 6c45917..e90aa16 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@f382bd1ea00f26043eb461ebabebe0d850572a71 # v0.0.24 + uses: anthropics/claude-code-base-action@ce5cfd683932f58cb459e749f20b06d2fb30c265 # v0.0.25 with: prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt allowed_tools: ${{ env.ALLOWED_TOOLS }} From 882586e4968e81b5df9ef7081d594daa203303b9 Mon Sep 17 00:00:00 2001 From: Tomohiro Ishibashi <103555868+tomoish@users.noreply.github.com> Date: Tue, 24 Jun 2025 09:39:14 +0900 Subject: [PATCH 11/12] chore: remove unwanted files added in commit 91f620f (#193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove example-dispatch-workflow.yml and pr-summary.md that were unintentionally added to the root directory in commit 91f620f. These files should not be in the repository root. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- example-dispatch-workflow.yml | 73 --------------------- pr-summary.md | 118 ---------------------------------- 2 files changed, 191 deletions(-) delete mode 100644 example-dispatch-workflow.yml delete mode 100644 pr-summary.md diff --git a/example-dispatch-workflow.yml b/example-dispatch-workflow.yml deleted file mode 100644 index 74cd95d..0000000 --- a/example-dispatch-workflow.yml +++ /dev/null @@ -1,73 +0,0 @@ -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 deleted file mode 100644 index 0830649..0000000 --- a/pr-summary.md +++ /dev/null @@ -1,118 +0,0 @@ -## 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 38254908ae505a7fc87cdc8ca3510717b8142803 Mon Sep 17 00:00:00 2001 From: Tomohiro Ishibashi <103555868+tomoish@users.noreply.github.com> Date: Tue, 24 Jun 2025 09:41:25 +0900 Subject: [PATCH 12/12] fix: allow direct_prompt with issue assignment without requiring assignee_trigger (#192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modified validation logic to only require assignee_trigger when direct_prompt is not provided - Made assigneeTrigger optional in IssueAssignedEvent type definition - Enhanced context generation to handle missing assigneeTrigger gracefully - Added comprehensive test coverage for the new behavior This enables direct_prompt workflows on issue assignment events without requiring assignee_trigger configuration, fixing the error: "ASSIGNEE_TRIGGER is required for issue assigned event" Fixes #113 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- src/create-prompt/index.ts | 8 +++--- src/create-prompt/types.ts | 2 +- test/create-prompt.test.ts | 23 +++++++++++++++++ test/prepare-context.test.ts | 49 ++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index d498cf9..27574d6 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -242,7 +242,7 @@ export function prepareContext( } if (eventAction === "assigned") { - if (!assigneeTrigger) { + if (!assigneeTrigger && !directPrompt) { throw new Error( "ASSIGNEE_TRIGGER is required for issue assigned event", ); @@ -254,7 +254,7 @@ export function prepareContext( issueNumber, baseBranch, claudeBranch, - assigneeTrigger, + ...(assigneeTrigger && { assigneeTrigger }), }; } else if (eventAction === "opened") { eventData = { @@ -331,7 +331,9 @@ export function getEventTypeAndContext(envVars: PreparedContext): { } return { eventType: "ISSUE_ASSIGNED", - triggerContext: `issue assigned to '${eventData.assigneeTrigger}'`, + triggerContext: eventData.assigneeTrigger + ? `issue assigned to '${eventData.assigneeTrigger}'` + : `issue assigned event`, }; case "pull_request": diff --git a/src/create-prompt/types.ts b/src/create-prompt/types.ts index 00bba5e..4d83d97 100644 --- a/src/create-prompt/types.ts +++ b/src/create-prompt/types.ts @@ -65,7 +65,7 @@ type IssueAssignedEvent = { issueNumber: string; baseBranch: string; claudeBranch: string; - assigneeTrigger: string; + assigneeTrigger?: string; }; type PullRequestEvent = { diff --git a/test/create-prompt.test.ts b/test/create-prompt.test.ts index 472ff65..b707b0f 100644 --- a/test/create-prompt.test.ts +++ b/test/create-prompt.test.ts @@ -614,6 +614,29 @@ describe("getEventTypeAndContext", () => { expect(result.eventType).toBe("ISSUE_ASSIGNED"); expect(result.triggerContext).toBe("issue assigned to 'claude-bot'"); }); + + test("should return correct type and context for issue assigned without assigneeTrigger", () => { + const envVars: PreparedContext = { + repository: "owner/repo", + claudeCommentId: "12345", + triggerPhrase: "@claude", + directPrompt: "Please assess this issue", + eventData: { + eventName: "issues", + eventAction: "assigned", + isPR: false, + issueNumber: "999", + baseBranch: "main", + claudeBranch: "claude/issue-999-20240101_120000", + // No assigneeTrigger when using directPrompt + }, + }; + + const result = getEventTypeAndContext(envVars); + + expect(result.eventType).toBe("ISSUE_ASSIGNED"); + expect(result.triggerContext).toBe("issue assigned event"); + }); }); describe("buildAllowedToolsString", () => { diff --git a/test/prepare-context.test.ts b/test/prepare-context.test.ts index 7811c5b..904dd37 100644 --- a/test/prepare-context.test.ts +++ b/test/prepare-context.test.ts @@ -219,6 +219,55 @@ describe("parseEnvVarsWithContext", () => { ), ).toThrow("BASE_BRANCH is required for issues event"); }); + + test("should allow issue assigned event with direct_prompt and no assigneeTrigger", () => { + const contextWithDirectPrompt = createMockContext({ + ...mockIssueAssignedContext, + inputs: { + ...mockIssueAssignedContext.inputs, + assigneeTrigger: "", // No assignee trigger + directPrompt: "Please assess this issue", // But direct prompt is provided + }, + }); + + const result = prepareContext( + contextWithDirectPrompt, + "12345", + "main", + "claude/issue-123-20240101_120000", + ); + + expect(result.eventData.eventName).toBe("issues"); + expect(result.eventData.isPR).toBe(false); + expect(result.directPrompt).toBe("Please assess this issue"); + if ( + result.eventData.eventName === "issues" && + result.eventData.eventAction === "assigned" + ) { + expect(result.eventData.issueNumber).toBe("123"); + expect(result.eventData.assigneeTrigger).toBeUndefined(); + } + }); + + test("should throw error when neither assigneeTrigger nor directPrompt provided for issue assigned event", () => { + const contextWithoutTriggers = createMockContext({ + ...mockIssueAssignedContext, + inputs: { + ...mockIssueAssignedContext.inputs, + assigneeTrigger: "", // No assignee trigger + directPrompt: "", // No direct prompt + }, + }); + + expect(() => + prepareContext( + contextWithoutTriggers, + "12345", + "main", + "claude/issue-123-20240101_120000", + ), + ).toThrow("ASSIGNEE_TRIGGER is required for issue assigned event"); + }); }); describe("optional fields", () => {