Compare commits

...

9 Commits

Author SHA1 Message Date
GitHub Actions
bcf2fe94f8 chore: update claude-code-base-action to v0.0.21 2025-06-17 13:39:54 +00:00
Ashwin Bhat
2dab3f2afe Revert "feat: enhance error reporting with specific error types from Claude e…" (#179)
This reverts commit 1b94b9e5a8.
2025-06-16 16:52:07 -07:00
Ashwin Bhat
1b94b9e5a8 feat: enhance error reporting with specific error types from Claude execution (#164)
* 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 <noreply@anthropic.com>

* 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 <ashwin-ant@users.noreply.github.com>

* 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 <noreply@anthropic.com>

* 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 <trigger_display_name> and <trigger_username> 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>

* 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 <ashwin-ant@users.noreply.github.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Actions <actions@github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
Co-authored-by: Bastian Gutschke <bge@medicuja.com>
Co-authored-by: Hidetake Iwata <int128@gmail.com>
Co-authored-by: Tomohiro Ishibashi <103555868+tomoish@users.noreply.github.com>
2025-06-16 15:31:43 -07:00
Tomohiro Ishibashi
e0d3fec39f update MCP server image to version 0.5.0 (#175) 2025-06-16 07:40:13 -07:00
GitHub Actions
3c748dc927 chore: update claude-code-base-action to v0.0.20 2025-06-14 02:45:07 +00:00
Ashwin Bhat
ffb2927088 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 <noreply@anthropic.com>
2025-06-13 17:43:56 -04:00
Ashwin Bhat
def1b3a94e 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 <ashwin-ant@users.noreply.github.com>
2025-06-13 14:15:50 -07:00
Hidetake Iwata
67d7753c80 Accept multiline input for allowed_tools and disallowed_tools (#168) 2025-06-13 10:19:36 -04:00
Bastian Gutschke
a8d323af27 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
2025-06-13 10:13:30 -04:00
8 changed files with 261 additions and 16 deletions

View File

@@ -32,7 +32,7 @@ jobs:
"--rm", "--rm",
"-e", "-e",
"GITHUB_PERSONAL_ACCESS_TOKEN", "GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server:sha-7aced2b" "ghcr.io/github/github-mcp-server:sha-6d69797"
], ],
"env": { "env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}" "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"

138
.github/workflows/release.yml vendored Normal file
View File

@@ -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"

View File

@@ -149,6 +149,40 @@ For MCP servers that require sensitive information like API keys or tokens, use
# ... other inputs # ... 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__<tool_name>" # Replace <tool_name> 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**: **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. - Always use GitHub Secrets (`${{ secrets.SECRET_NAME }}`) for sensitive values like API keys, tokens, or passwords. Never hardcode secrets directly in the workflow file.
@@ -347,8 +381,15 @@ Claude does **not** have access to execute arbitrary Bash commands by default. I
```yaml ```yaml
- uses: anthropics/claude-code-action@beta - uses: anthropics/claude-code-action@beta
with: with:
allowed_tools: "Bash(npm install),Bash(npm run test),Edit,Replace,NotebookEditCell" allowed_tools: |
disallowed_tools: "TaskOutput,KillTask" Bash(npm install)
Bash(npm run test)
Edit
Replace
NotebookEditCell
disallowed_tools: |
TaskOutput
KillTask
# ... other inputs # ... other inputs
``` ```

View File

@@ -110,7 +110,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@ebd8558e902b3db132e89863de49565fcb9aec46 # v0.0.19 uses: anthropics/claude-code-base-action@cef27f3f006b4c6e8394105604f63f20e84ae300 # v0.0.21
with: with:
prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
allowed_tools: ${{ env.ALLOWED_TOOLS }} allowed_tools: ${{ env.ALLOWED_TOOLS }}

View File

@@ -52,14 +52,8 @@ export function parseGitHubContext(): ParsedGitHubContext {
inputs: { inputs: {
triggerPhrase: process.env.TRIGGER_PHRASE ?? "@claude", triggerPhrase: process.env.TRIGGER_PHRASE ?? "@claude",
assigneeTrigger: process.env.ASSIGNEE_TRIGGER ?? "", assigneeTrigger: process.env.ASSIGNEE_TRIGGER ?? "",
allowedTools: (process.env.ALLOWED_TOOLS ?? "") allowedTools: parseMultilineInput(process.env.ALLOWED_TOOLS ?? ""),
.split(",") disallowedTools: parseMultilineInput(process.env.DISALLOWED_TOOLS ?? ""),
.map((tool) => tool.trim())
.filter((tool) => tool.length > 0),
disallowedTools: (process.env.DISALLOWED_TOOLS ?? "")
.split(",")
.map((tool) => tool.trim())
.filter((tool) => tool.length > 0),
customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "", customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "",
directPrompt: process.env.DIRECT_PROMPT ?? "", directPrompt: process.env.DIRECT_PROMPT ?? "",
baseBranch: process.env.BASE_BRANCH, 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( export function isIssuesEvent(
context: ParsedGitHubContext, context: ParsedGitHubContext,
): context is ParsedGitHubContext & { payload: IssuesEvent } { ): context is ParsedGitHubContext & { payload: IssuesEvent } {

View File

@@ -45,9 +45,16 @@ export async function setupBranch(
const branchName = prData.headRefName; const branchName = prData.headRefName;
// Execute git commands to checkout PR branch (shallow fetch for performance) // Determine optimal fetch depth based on PR commit count, with a minimum of 20
// Fetch the branch with a depth of 20 to avoid fetching too much history, while still allowing for some context const commitCount = prData.commits.totalCount;
await $`git fetch origin --depth=20 ${branchName}`; 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}`; await $`git checkout ${branchName}`;
console.log(`Successfully checked out PR branch for PR #${entityNumber}`); console.log(`Successfully checked out PR branch for PR #${entityNumber}`);

View File

@@ -62,7 +62,7 @@ export async function prepareMcpConfig(
"--rm", "--rm",
"-e", "-e",
"GITHUB_PERSONAL_ACCESS_TOKEN", "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: { env: {
GITHUB_PERSONAL_ACCESS_TOKEN: githubToken, GITHUB_PERSONAL_ACCESS_TOKEN: githubToken,

View File

@@ -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([]);
});
});