Compare commits

..

20 Commits

Author SHA1 Message Date
Ashwin Bhat
44e276ae4f refactor: simplify error display to show clean error messages only
- Remove collapsible <details> section for error messages
- Display errors in simple code blocks since messages are now clean and short
- Makes error messages more direct and readable

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-28 17:58:07 -07:00
Ashwin Bhat
baa1ecb265 refactor: simplify error capture to show clean error messages only
- Remove complex shell script that captured full output logs
- Use core.setOutput in prepare.ts to pass clean error message directly
- Avoid exposing potentially sensitive information from logs
- Show only the actual error message (e.g. 'Failed to fetch issue data')

This provides cleaner, more readable error messages without the risk
of exposing sensitive information from debug logs.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-28 17:50:45 -07:00
Ashwin Bhat
81181ca658 feat: display detailed error messages when prepare step fails
- Capture prepare step errors in action.yml (up to 2000 chars)
- Add error details to comment update with collapsible section
- Handle both prepare and Claude execution failures separately
- Add test coverage for error detail display

This helps users debug issues like git errors, permission problems,
and branch creation failures more easily.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-28 17:45:11 -07:00
Ashwin Bhat
176dbc369d bump base action to 0.0.6 (#79) 2025-05-28 13:19:10 -07:00
Erjan K
8ae72a97c6 Fix readme typo (#58) 2025-05-28 10:20:00 -07:00
Ashwin Bhat
0eb34ae441 Add shallow fetch to improve performance for large repositories (#53)
* Add shallow fetch to improve performance for large repositories

This change adds `--depth=1` to git fetch operations to perform shallow
fetches instead of full history downloads. This significantly reduces
checkout time for large repositories as reported in issue #52.

Changes:
- Line 55: Added --depth=1 to PR branch fetch
- Line 102: Added --depth=1 to new branch fetch

Fixes #52

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>

* fetch 50 commits for PRs

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-05-27 16:31:06 -07:00
Ashwin Bhat
804959ac41 add issue triage workflow (#70) 2025-05-27 14:04:41 -07:00
Ashwin Bhat
21e17bd590 remove .DS_Store (#69) 2025-05-27 13:26:03 -07:00
Ashwin Bhat
4b925ddf0c Update issue templates (#51) 2025-05-27 13:18:29 -07:00
Ashwin Bhat
253f2c6796 Pin GitHub Action dependencies to commit SHAs for security (#66)
Pin oven-sh/setup-bun and anthropics/claude-code-base-action to specific commit SHAs instead of version tags to ensure reproducible builds and improve supply chain security.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-27 10:14:11 -07:00
Ashwin Bhat
3c6a85b54b Improve error messages for GitHub Action authentication failures (#50)
- Add helpful hint about workflow permissions when OIDC token is not found
- Include response body in app token exchange failure errors for better debugging

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-25 18:43:54 -07:00
Lina Tawfik
cbc3ca285d Merge pull request #39 from anthropics/fix-mcp-undefined-error
Fix MCP file operations server errors
2025-05-23 12:30:01 -07:00
Lina Tawfik
f3bfb2a9ad Merge pull request #34 from anthropics/update-claude-workflow-v2
Update Claude workflow
2025-05-22 21:51:16 -07:00
Lina Tawfik
36c5ee33cd Update Claude workflow
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-22 21:43:54 -07:00
Lina Tawfik
8e84799f37 Merge pull request #25 from anthropics/update-to-use-model-parameter
Udpate claude model to default -p model
2025-05-22 11:02:14 -07:00
Lina Tawfik
b129b800c5 Merge pull request #23 from anthropics/np-anthropic-patch-1
Add graphic to readme
2025-05-22 09:26:50 -07:00
Lina Tawfik
80dbb4a5aa Merge pull request #24 from anthropics/update-to-use-model-parameter
Update to use model parameter in claude-code-base-action
2025-05-22 09:19:03 -07:00
Nate Parrott
be7f75d65a fix formatting 2025-05-22 11:12:51 -04:00
np-anthropic
e3d126d058 Add graphic to readme 2025-05-22 10:57:20 -04:00
Lina Tawfik
11f5812e28 Merge pull request #22 from anthropics/lina/rename-anthropic-model-to-model
Rename anthropic_model input to model with backward compatibility
2025-05-22 07:26:42 -07:00
14 changed files with 245 additions and 57 deletions

BIN
.DS_Store vendored

Binary file not shown.

36
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,36 @@
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: bug
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Workflow yml file**
If it's not sensitive, consider including a paste of your full Claude workflow.yml file.
**API Provider**
[ ] Anthropic First-Party API (default)
[ ] AWS Bedrock
[ ] GCP Vertex
**Additional context**
Add any other context about the problem here.

View File

@@ -1,4 +1,4 @@
name: Claude name: Claude Code
on: on:
issue_comment: issue_comment:
@@ -11,12 +11,12 @@ on:
types: [submitted] types: [submitted]
jobs: jobs:
claude-pr: claude:
if: | if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && contains(github.event.issue.body, '@claude')) (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -29,10 +29,8 @@ jobs:
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Run Claude PR Agent - name: Run Claude Code
uses: anthropics/claude-code-action@main id: claude
uses: anthropics/claude-code-action@beta
with: with:
timeout_minutes: "60"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
allowed_tools: "Bash(bun install),Bash(bun test:*),Bash(bun run format),Bash(bun typecheck)"
custom_instructions: "You have also been granted tools for editing files and running bun commands (install, run, test) for testing your changes."

104
.github/workflows/issue-triage.yml vendored Normal file
View File

@@ -0,0 +1,104 @@
name: Claude Issue Triage
description: Run Claude Code for issue triage in GitHub Actions
on:
issues:
types: [opened]
jobs:
triage-issue:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup GitHub MCP Server
run: |
mkdir -p /tmp/mcp-config
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
{
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server:sha-7aced2b"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
}
}
}
EOF
- name: Create triage prompt
run: |
mkdir -p /tmp/claude-prompts
cat > /tmp/claude-prompts/triage-prompt.txt << 'EOF'
You're an issue triage assistant for GitHub issues. Your task is to analyze the issue and select appropriate labels from the provided list.
IMPORTANT: Don't post any comments or messages to the issue. Your only action should be to apply labels.
Issue Information:
- REPO: ${{ github.repository }}
- ISSUE_NUMBER: ${{ github.event.issue.number }}
TASK OVERVIEW:
1. First, fetch the list of labels available in this repository by running: `gh label list`. Run exactly this command with nothing else.
2. Next, use the GitHub tools to get context about the issue:
- You have access to these tools:
- mcp__github__get_issue: Use this to retrieve the current issue's details including title, description, and existing labels
- mcp__github__get_issue_comments: Use this to read any discussion or additional context provided in the comments
- mcp__github__update_issue: Use this to apply labels to the issue (do not use this for commenting)
- mcp__github__search_issues: Use this to find similar issues that might provide context for proper categorization and to identify potential duplicate issues
- mcp__github__list_issues: Use this to understand patterns in how other issues are labeled
- Start by using mcp__github__get_issue to get the issue details
3. Analyze the issue content, considering:
- The issue title and description
- The type of issue (bug report, feature request, question, etc.)
- Technical areas mentioned
- Severity or priority indicators
- User impact
- Components affected
4. Select appropriate labels from the available labels list provided above:
- Choose labels that accurately reflect the issue's nature
- Be specific but comprehensive
- Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority)
- Consider platform labels (android, ios) if applicable
- If you find similar issues using mcp__github__search_issues, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
5. Apply the selected labels:
- Use mcp__github__update_issue to apply your selected labels
- DO NOT post any comments explaining your decision
- DO NOT communicate directly with users
- If no labels are clearly applicable, do not apply any labels
IMPORTANT GUIDELINES:
- Be thorough in your analysis
- Only select labels from the provided list above
- DO NOT post any comments to the issue
- Your ONLY action should be to apply labels using mcp__github__update_issue
- It's okay to not add any labels if none are clearly applicable
EOF
- name: Run Claude Code for Issue Triage
uses: anthropics/claude-code-base-action@beta
with:
prompt_file: /tmp/claude-prompts/triage-prompt.txt
allowed_tools: "Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues"
mcp_config_file: /tmp/mcp-config/mcp-servers.json
timeout_minutes: "5"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.DS_Store
node_modules node_modules
**/.claude/settings.local.json **/.claude/settings.local.json

View File

@@ -1,3 +1,5 @@
![Claude Code Action responding to a comment](https://github.com/user-attachments/assets/1d60c2e9-82ed-4ee5-b749-f9e021c85f4d)
# Claude Code Action # Claude Code Action
A general-purpose [Claude Code](https://claude.ai/code) action for GitHub PRs and issues that can answer questions and implement code changes. This action listens for a trigger phrase in comments and activates Claude act on the request. It supports multiple authentication methods including Anthropic direct API, Amazon Bedrock, and Google Vertex AI. A general-purpose [Claude Code](https://claude.ai/code) action for GitHub PRs and issues that can answer questions and implement code changes. This action listens for a trigger phrase in comments and activates Claude act on the request. It supports multiple authentication methods including Anthropic direct API, Amazon Bedrock, and Google Vertex AI.
@@ -444,7 +446,7 @@ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
``` ```
This applies to all sensitive values including API keys, access tokens, and credentials. This applies to all sensitive values including API keys, access tokens, and credentials.
We also reccomend that you always use short-lived tokens when possible We also recommend that you always use short-lived tokens when possible
## License ## License

View File

@@ -67,7 +67,7 @@ runs:
using: "composite" using: "composite"
steps: steps:
- name: Install Bun - name: Install Bun
uses: oven-sh/setup-bun@v2 uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2
with: with:
bun-version: 1.2.11 bun-version: 1.2.11
@@ -94,7 +94,7 @@ runs:
- name: Run Claude Code - name: Run Claude Code
id: claude-code id: claude-code
if: steps.prepare.outputs.contains_trigger == 'true' if: steps.prepare.outputs.contains_trigger == 'true'
uses: anthropics/claude-code-base-action@beta uses: anthropics/claude-code-base-action@266585c92dd90d61d3806a3367582c4f6224e892 # https://github.com/anthropics/claude-code-base-action/releases/tag/v0.0.6
with: with:
prompt_file: /tmp/claude-prompts/claude-prompt.txt prompt_file: /tmp/claude-prompts/claude-prompt.txt
allowed_tools: ${{ env.ALLOWED_TOOLS }} allowed_tools: ${{ env.ALLOWED_TOOLS }}
@@ -147,6 +147,8 @@ runs:
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }} CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }} OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }}
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }} TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}
PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
- name: Display Claude Code Report - name: Display Claude Code Report
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != '' if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''

BIN
src/.DS_Store vendored

Binary file not shown.

View File

@@ -92,7 +92,10 @@ async function run() {
); );
core.setOutput("mcp_config", mcpConfig); core.setOutput("mcp_config", mcpConfig);
} catch (error) { } catch (error) {
core.setFailed(`Prepare step failed with error: ${error}`); const errorMessage = error instanceof Error ? error.message : String(error);
core.setFailed(`Prepare step failed with error: ${errorMessage}`);
// Also output the clean error message for the action to capture
core.setOutput("prepare_error", errorMessage);
process.exit(1); process.exit(1);
} }
} }

View File

@@ -145,7 +145,16 @@ async function run() {
duration_api_ms?: number; duration_api_ms?: number;
} | null = null; } | null = null;
let actionFailed = false; let actionFailed = false;
let errorDetails: string | undefined;
// First check if prepare step failed
const prepareSuccess = process.env.PREPARE_SUCCESS !== "false";
const prepareError = process.env.PREPARE_ERROR;
if (!prepareSuccess && prepareError) {
actionFailed = true;
errorDetails = prepareError;
} else {
// Check for existence of output file and parse it if available // Check for existence of output file and parse it if available
try { try {
const outputFile = process.env.OUTPUT_FILE; const outputFile = process.env.OUTPUT_FILE;
@@ -170,7 +179,7 @@ async function run() {
} }
} }
// Check if the action failed by looking at the exit code or error marker // Check if the Claude action failed
const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false"; const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false";
actionFailed = !claudeSuccess; actionFailed = !claudeSuccess;
} catch (error) { } catch (error) {
@@ -178,6 +187,7 @@ async function run() {
// If we can't read the file, check for any failure markers // If we can't read the file, check for any failure markers
actionFailed = process.env.CLAUDE_SUCCESS === "false"; actionFailed = process.env.CLAUDE_SUCCESS === "false";
} }
}
// Prepare input for updateCommentBody function // Prepare input for updateCommentBody function
const commentInput: CommentUpdateInput = { const commentInput: CommentUpdateInput = {
@@ -189,6 +199,7 @@ async function run() {
prLink, prLink,
branchName: shouldDeleteBranch ? undefined : claudeBranch, branchName: shouldDeleteBranch ? undefined : claudeBranch,
triggerUsername, triggerUsername,
errorDetails,
}; };
const updatedBody = updateCommentBody(commentInput); const updatedBody = updateCommentBody(commentInput);

View File

@@ -51,8 +51,9 @@ export async function setupBranch(
const branchName = prData.headRefName; const branchName = prData.headRefName;
// Execute git commands to checkout PR branch // Execute git commands to checkout PR branch (shallow fetch for performance)
await $`git fetch origin ${branchName}`; // 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}`;
await $`git checkout ${branchName}`; await $`git checkout ${branchName}`;
console.log(`Successfully checked out PR branch for PR #${entityNumber}`); console.log(`Successfully checked out PR branch for PR #${entityNumber}`);
@@ -98,8 +99,8 @@ export async function setupBranch(
sha: currentSHA, sha: currentSHA,
}); });
// Checkout the new branch // Checkout the new branch (shallow fetch for performance)
await $`git fetch origin ${newBranch}`; await $`git fetch origin --depth=1 ${newBranch}`;
await $`git checkout ${newBranch}`; await $`git checkout ${newBranch}`;
console.log( console.log(

View File

@@ -15,6 +15,7 @@ export type CommentUpdateInput = {
prLink?: string; prLink?: string;
branchName?: string; branchName?: string;
triggerUsername?: string; triggerUsername?: string;
errorDetails?: string;
}; };
export function ensureProperlyEncodedUrl(url: string): string | null { export function ensureProperlyEncodedUrl(url: string): string | null {
@@ -75,6 +76,7 @@ export function updateCommentBody(input: CommentUpdateInput): string {
actionFailed, actionFailed,
branchName, branchName,
triggerUsername, triggerUsername,
errorDetails,
} = input; } = input;
// Extract content from the original comment body // Extract content from the original comment body
@@ -177,7 +179,14 @@ export function updateCommentBody(input: CommentUpdateInput): string {
} }
// Build the new body with blank line between header and separator // Build the new body with blank line between header and separator
let newBody = `${header}${links}\n\n---\n`; let newBody = `${header}${links}`;
// Add error details if available
if (actionFailed && errorDetails) {
newBody += `\n\n\`\`\`\n${errorDetails}\n\`\`\``;
}
newBody += `\n\n---\n`;
// Clean up the body content // Clean up the body content
// Remove any existing View job run, branch links from the bottom // Remove any existing View job run, branch links from the bottom

View File

@@ -39,25 +39,19 @@ async function retryWithBackoff<T>(
} }
} }
throw new Error( console.error(`Operation failed after ${maxAttempts} attempts`);
`Operation failed after ${maxAttempts} attempts. Last error: ${ throw lastError;
lastError?.message ?? "Unknown error"
}`,
);
} }
async function getOidcToken(): Promise<string> { async function getOidcToken(): Promise<string> {
try { try {
const oidcToken = await core.getIDToken("claude-code-github-action"); const oidcToken = await core.getIDToken("claude-code-github-action");
if (!oidcToken) {
throw new Error("OIDC token not found");
}
return oidcToken; return oidcToken;
} catch (error) { } catch (error) {
console.error("Failed to get OIDC token:", error);
throw new Error( throw new Error(
`Failed to get OIDC token: ${error instanceof Error ? error.message : String(error)}`, "Could not fetch an OIDC token. Did you remember to add `id-token: write` to your workflow permissions?",
); );
} }
} }
@@ -74,9 +68,15 @@ async function exchangeForAppToken(oidcToken: string): Promise<string> {
); );
if (!response.ok) { if (!response.ok) {
throw new Error( const responseJson = (await response.json()) as {
`App token exchange failed: ${response.status} ${response.statusText}`, error?: {
message?: string;
};
};
console.error(
`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson?.error?.message ?? "Unknown error"}`,
); );
throw new Error(`${responseJson?.error?.message ?? "Unknown error"}`);
} }
const appTokenData = (await response.json()) as { const appTokenData = (await response.json()) as {
@@ -117,7 +117,9 @@ export async function setupGitHubToken(): Promise<string> {
core.setOutput("GITHUB_TOKEN", appToken); core.setOutput("GITHUB_TOKEN", appToken);
return appToken; return appToken;
} catch (error) { } catch (error) {
core.setFailed(`Failed to setup GitHub token: ${error}`); core.setFailed(
`Failed to setup GitHub token: ${error}.\n\nIf you instead wish to use this action with a custom GitHub token or custom GitHub app, provide a \`github_token\` in the \`uses\` section of the app in your workflow yml file.`,
);
process.exit(1); process.exit(1);
} }
} }

View File

@@ -39,6 +39,25 @@ describe("updateCommentBody", () => {
expect(result).toContain("**Claude encountered an error after 45s**"); expect(result).toContain("**Claude encountered an error after 45s**");
}); });
it("includes error details when provided", () => {
const input = {
...baseInput,
currentBody: "Claude Code is working...",
actionFailed: true,
executionDetails: { duration_ms: 45000 },
errorDetails: "Failed to fetch issue data",
};
const result = updateCommentBody(input);
expect(result).toContain("**Claude encountered an error after 45s**");
expect(result).toContain("[View job]");
expect(result).toContain("```\nFailed to fetch issue data\n```");
// Ensure error details come after the header/links
const errorIndex = result.indexOf("```");
const headerIndex = result.indexOf("**Claude encountered an error");
expect(errorIndex).toBeGreaterThan(headerIndex);
});
it("handles username extraction from content when not provided", () => { it("handles username extraction from content when not provided", () => {
const input = { const input = {
...baseInput, ...baseInput,