mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
Compare commits
41 Commits
ashwin/bra
...
add-claude
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
721b4c6d2f | ||
|
|
67caede2ad | ||
|
|
fd20c95358 | ||
|
|
d808160c26 | ||
|
|
3eacedbeb7 | ||
|
|
f52f12eba5 | ||
|
|
4a85933f25 | ||
|
|
ba6edd55ef | ||
|
|
06461dddff | ||
|
|
c2a94eead0 | ||
|
|
1c0c3eaced | ||
|
|
23d2d6c6b4 | ||
|
|
e8bad57227 | ||
|
|
0a6d62601b | ||
|
|
777ffcbfc9 | ||
|
|
dc58efed33 | ||
|
|
e5437bfbc5 | ||
|
|
b2dd1006a0 | ||
|
|
ac1a3207f3 | ||
|
|
521d069da7 | ||
|
|
7e4b782d5f | ||
|
|
4fb0ef3be0 | ||
|
|
14ac8aa20e | ||
|
|
90d189f3ab | ||
|
|
9c09b26b2d | ||
|
|
2086c977a5 | ||
|
|
851ef5b84e | ||
|
|
1ce8153c18 | ||
|
|
00391ab25e | ||
|
|
426380f01b | ||
|
|
77f51d2905 | ||
|
|
7e5b42b197 | ||
|
|
1b7c7a77d3 | ||
|
|
bd70a3ef2b | ||
|
|
f4954b5256 | ||
|
|
93f8ab56c2 | ||
|
|
93028b410e | ||
|
|
838d4d9d25 | ||
|
|
7ed3b616d5 | ||
|
|
09ea2f00e1 | ||
|
|
455b943dd7 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
prettier:
|
prettier:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
typecheck:
|
typecheck:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
57
.github/workflows/claude-code-review.yml
vendored
Normal file
57
.github/workflows/claude-code-review.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: Claude Code Review
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize]
|
||||||
|
# Optional: Only run on specific file changes
|
||||||
|
# paths:
|
||||||
|
# - "src/**/*.ts"
|
||||||
|
# - "src/**/*.tsx"
|
||||||
|
# - "src/**/*.js"
|
||||||
|
# - "src/**/*.jsx"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
claude-review:
|
||||||
|
# Optional: Filter by PR author
|
||||||
|
# if: |
|
||||||
|
# github.event.pull_request.user.login == 'external-contributor' ||
|
||||||
|
# github.event.pull_request.user.login == 'new-developer' ||
|
||||||
|
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
issues: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run Claude Code Review
|
||||||
|
id: claude-review
|
||||||
|
uses: anthropics/claude-code-action@v1
|
||||||
|
with:
|
||||||
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
prompt: |
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
PR NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
|
Please review this pull request and provide feedback on:
|
||||||
|
- Code quality and best practices
|
||||||
|
- Potential bugs or issues
|
||||||
|
- Performance considerations
|
||||||
|
- Security concerns
|
||||||
|
- Test coverage
|
||||||
|
|
||||||
|
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
||||||
|
|
||||||
|
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
|
||||||
|
|
||||||
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||||
|
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
||||||
|
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
||||||
|
|
||||||
2
.github/workflows/claude-review.yml
vendored
2
.github/workflows/claude-review.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
38
.github/workflows/claude-test.yml
vendored
38
.github/workflows/claude-test.yml
vendored
@@ -1,38 +0,0 @@
|
|||||||
# Test workflow for km-anthropic fork (v1-dev branch)
|
|
||||||
# This tests the fork implementation, not the main repo
|
|
||||||
name: Claude Code (Fork Test)
|
|
||||||
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
pull_request_review_comment:
|
|
||||||
types: [created]
|
|
||||||
issues:
|
|
||||||
types: [opened, assigned]
|
|
||||||
pull_request_review:
|
|
||||||
types: [submitted]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
claude:
|
|
||||||
if: |
|
|
||||||
(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' && contains(github.event.review.body, '@claude')) ||
|
|
||||||
(github.event_name == 'issues' && (
|
|
||||||
contains(github.event.issue.body, '@claude') ||
|
|
||||||
contains(github.event.issue.title, '@claude')
|
|
||||||
))
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
id-token: write # Required for OIDC token exchange
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run Claude Code
|
|
||||||
uses: km-anthropic/claude-code-action@v1-dev
|
|
||||||
with:
|
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
||||||
17
.github/workflows/claude.yml
vendored
17
.github/workflows/claude.yml
vendored
@@ -23,6 +23,7 @@ jobs:
|
|||||||
pull-requests: read
|
pull-requests: read
|
||||||
issues: read
|
issues: read
|
||||||
id-token: write
|
id-token: write
|
||||||
|
actions: read # Required for Claude to read CI results on PRs
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -34,6 +35,16 @@ jobs:
|
|||||||
uses: anthropics/claude-code-action@v1
|
uses: anthropics/claude-code-action@v1
|
||||||
with:
|
with:
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
claude_args: |
|
|
||||||
--allowedTools "Bash(bun install),Bash(bun test:*),Bash(bun run format),Bash(bun typecheck)"
|
# This is an optional setting that allows Claude to read CI results on PRs
|
||||||
--model "claude-opus-4-1-20250805"
|
additional_permissions: |
|
||||||
|
actions: read
|
||||||
|
|
||||||
|
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
||||||
|
# prompt: 'Update the pull request description to include a summary of changes.'
|
||||||
|
|
||||||
|
# Optional: Add claude_args to customize behavior and configuration
|
||||||
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||||
|
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
||||||
|
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/issue-triage.yml
vendored
2
.github/workflows/issue-triage.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
next_version: ${{ steps.next_version.outputs.next_version }}
|
next_version: ${{ steps.next_version.outputs.next_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ jobs:
|
|||||||
environment: production
|
environment: production
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout base-action repo
|
- name: Checkout base-action repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: anthropics/claude-code-base-action
|
repository: anthropics/claude-code-base-action
|
||||||
token: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}
|
token: ${{ secrets.CLAUDE_CODE_BASE_ACTION_PAT }}
|
||||||
|
|||||||
4
.github/workflows/test-settings.yml
vendored
4
.github/workflows/test-settings.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
|||||||
uses: ./base-action
|
uses: ./base-action
|
||||||
with:
|
with:
|
||||||
prompt: |
|
prompt: |
|
||||||
Use Bash to echo "This should not work"
|
Run the command `echo $HOME` to check the home directory path
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
settings: |
|
settings: |
|
||||||
{
|
{
|
||||||
@@ -163,7 +163,7 @@ jobs:
|
|||||||
uses: ./base-action
|
uses: ./base-action
|
||||||
with:
|
with:
|
||||||
prompt: |
|
prompt: |
|
||||||
Use Bash to echo "This should not work from file"
|
Run the command `echo $HOME` to check the home directory path
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
settings: "test-settings.json"
|
settings: "test-settings.json"
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ runs:
|
|||||||
# Install Claude Code if no custom executable is provided
|
# Install Claude Code if no custom executable is provided
|
||||||
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
|
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
|
||||||
echo "Installing Claude Code..."
|
echo "Installing Claude Code..."
|
||||||
curl -fsSL https://claude.ai/install.sh | bash -s 1.0.113
|
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.24
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||||
else
|
else
|
||||||
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
|
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
|
||||||
@@ -259,7 +259,7 @@ runs:
|
|||||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||||
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
|
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
|
||||||
CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
|
CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
|
||||||
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }}
|
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review_comment' }}
|
||||||
BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }}
|
BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }}
|
||||||
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 || '' }}
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ runs:
|
|||||||
run: |
|
run: |
|
||||||
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
|
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then
|
||||||
echo "Installing Claude Code..."
|
echo "Installing Claude Code..."
|
||||||
curl -fsSL https://claude.ai/install.sh | bash -s 1.0.113
|
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.24
|
||||||
else
|
else
|
||||||
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
|
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}"
|
||||||
# Add the directory containing the custom executable to PATH
|
# Add the directory containing the custom executable to PATH
|
||||||
|
|||||||
@@ -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-23fa0dd"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
|
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ The action automatically detects which mode to use based on your configuration:
|
|||||||
|
|
||||||
This action supports the following GitHub events ([learn more GitHub event triggers](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows)):
|
This action supports the following GitHub events ([learn more GitHub event triggers](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows)):
|
||||||
|
|
||||||
- `pull_request` - When PRs are opened or synchronized
|
- `pull_request` or `pull_request_target` - When PRs are opened or synchronized
|
||||||
- `issue_comment` - When comments are created on issues or PRs
|
- `issue_comment` - When comments are created on issues or PRs
|
||||||
- `pull_request_comment` - When comments are made on PR diffs
|
- `pull_request_comment` - When comments are made on PR diffs
|
||||||
- `issues` - When issues are opened or assigned
|
- `issues` - When issues are opened or assigned
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ For performance, Claude uses shallow clones:
|
|||||||
If you need full history, you can configure this in your workflow before calling Claude in the `actions/checkout` step.
|
If you need full history, you can configure this in your workflow before calling Claude in the `actions/checkout` step.
|
||||||
|
|
||||||
```
|
```
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
depth: 0 # will fetch full repo history
|
depth: 0 # will fetch full repo history
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,28 @@
|
|||||||
- **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered
|
- **No Cross-Repository Access**: Each action invocation is limited to the repository where it was triggered
|
||||||
- **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions
|
- **Limited Scope**: The token cannot access other repositories or perform actions beyond the configured permissions
|
||||||
|
|
||||||
|
## ⚠️ Prompt Injection Risks
|
||||||
|
|
||||||
|
**Beware of potential hidden markdown when tagging Claude on untrusted content.** External contributors may include hidden instructions through HTML comments, invisible characters, hidden attributes, or other techniques. The action sanitizes content by stripping HTML comments, invisible characters, markdown image alt text, hidden HTML attributes, and HTML entities, but new bypass techniques may emerge. We recommend reviewing the raw content of all input coming from external contributors before allowing Claude to process it.
|
||||||
|
|
||||||
## GitHub App Permissions
|
## GitHub App Permissions
|
||||||
|
|
||||||
The [Claude Code GitHub app](https://github.com/apps/claude) requires these permissions:
|
The [Claude Code GitHub app](https://github.com/apps/claude) requests the following permissions:
|
||||||
|
|
||||||
- **Pull Requests**: Read and write to create PRs and push changes
|
### Currently Used Permissions
|
||||||
- **Issues**: Read and write to respond to issues
|
|
||||||
- **Contents**: Read and write to modify repository files
|
- **Contents** (Read & Write): For reading repository files and creating branches
|
||||||
|
- **Pull Requests** (Read & Write): For reading PR data and creating/updating pull requests
|
||||||
|
- **Issues** (Read & Write): For reading issue data and updating issue comments
|
||||||
|
|
||||||
|
### Permissions for Future Features
|
||||||
|
|
||||||
|
The following permissions are requested but not yet actively used. These will enable planned features in future releases:
|
||||||
|
|
||||||
|
- **Discussions** (Read & Write): For interaction with GitHub Discussions
|
||||||
|
- **Actions** (Read): For accessing workflow run data and logs
|
||||||
|
- **Checks** (Read): For reading check run results
|
||||||
|
- **Workflows** (Read & Write): For triggering and managing GitHub Actions workflows
|
||||||
|
|
||||||
## Commit Signing
|
## Commit Signing
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
@@ -344,7 +344,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -456,7 +456,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -513,7 +513,7 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ jobs:
|
|||||||
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
|
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
|
||||||
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
|
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | `false` |
|
||||||
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
|
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | `false` |
|
||||||
| `mcp_config` | Additional MCP configuration (JSON string) that merges with the built-in GitHub MCP servers | No | "" |
|
|
||||||
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
|
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
|
||||||
| `label_trigger` | The label name that triggers the action when applied to an issue (e.g. "claude") | No | - |
|
| `label_trigger` | The label name that triggers the action when applied to an issue (e.g. "claude") | No | - |
|
||||||
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
|
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
|
||||||
@@ -90,6 +89,7 @@ These inputs are deprecated and will be removed in a future version:
|
|||||||
| `fallback_model` | **DEPRECATED**: Use `claude_args` with fallback configuration | Configure fallback in `claude_args` or `settings` |
|
| `fallback_model` | **DEPRECATED**: Use `claude_args` with fallback configuration | Configure fallback in `claude_args` or `settings` |
|
||||||
| `allowed_tools` | **DEPRECATED**: Use `claude_args` with `--allowedTools` instead | Use `claude_args: "--allowedTools Edit,Read,Write"` |
|
| `allowed_tools` | **DEPRECATED**: Use `claude_args` with `--allowedTools` instead | Use `claude_args: "--allowedTools Edit,Read,Write"` |
|
||||||
| `disallowed_tools` | **DEPRECATED**: Use `claude_args` with `--disallowedTools` instead | Use `claude_args: "--disallowedTools WebSearch"` |
|
| `disallowed_tools` | **DEPRECATED**: Use `claude_args` with `--disallowedTools` instead | Use `claude_args: "--disallowedTools WebSearch"` |
|
||||||
|
| `mcp_config` | **DEPRECATED**: Use `claude_args` with `--mcp-config` instead | Use `claude_args: "--mcp-config '{...}'"` |
|
||||||
| `claude_env` | **DEPRECATED**: Use `settings` with env configuration | Configure environment in `settings` JSON |
|
| `claude_env` | **DEPRECATED**: Use `settings` with env configuration | Configure environment in `settings` JSON |
|
||||||
|
|
||||||
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)
|
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.workflow_run.head_branch }}
|
ref: ${{ github.event.workflow_run.head_branch }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
actions: read # Required for Claude to read CI results on PRs
|
actions: read # Required for Claude to read CI results on PRs
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2 # Need at least 2 commits to analyze the latest
|
fetch-depth: 2 # Need at least 2 commits to analyze the latest
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ export function getEventTypeAndContext(envVars: PreparedContext): {
|
|||||||
};
|
};
|
||||||
|
|
||||||
case "pull_request":
|
case "pull_request":
|
||||||
|
case "pull_request_target":
|
||||||
return {
|
return {
|
||||||
eventType: "PULL_REQUEST",
|
eventType: "PULL_REQUEST",
|
||||||
triggerContext: eventData.eventAction
|
triggerContext: eventData.eventAction
|
||||||
@@ -708,7 +709,7 @@ What You CANNOT Do:
|
|||||||
- Modify files in the .github/workflows directory (GitHub App permissions do not allow workflow modifications)
|
- Modify files in the .github/workflows directory (GitHub App permissions do not allow workflow modifications)
|
||||||
|
|
||||||
When users ask you to perform actions you cannot do, politely explain the limitation and, when applicable, direct them to the FAQ for more information and workarounds:
|
When users ask you to perform actions you cannot do, politely explain the limitation and, when applicable, direct them to the FAQ for more information and workarounds:
|
||||||
"I'm unable to [specific action] due to [reason]. You can find more information and potential workarounds in the [FAQ](https://github.com/anthropics/claude-code-action/blob/main/FAQ.md)."
|
"I'm unable to [specific action] due to [reason]. You can find more information and potential workarounds in the [FAQ](https://github.com/anthropics/claude-code-action/blob/main/docs/faq.md)."
|
||||||
|
|
||||||
If a user asks for something outside these capabilities (and you have no other tools provided), politely explain that you cannot perform that action and suggest an alternative approach if possible.
|
If a user asks for something outside these capabilities (and you have no other tools provided), politely explain that you cannot perform that action and suggest an alternative approach if possible.
|
||||||
|
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ type IssueLabeledEvent = {
|
|||||||
labelTrigger: string;
|
labelTrigger: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PullRequestEvent = {
|
type PullRequestBaseEvent = {
|
||||||
eventName: "pull_request";
|
|
||||||
eventAction?: string; // opened, synchronize, etc.
|
eventAction?: string; // opened, synchronize, etc.
|
||||||
isPR: true;
|
isPR: true;
|
||||||
prNumber: string;
|
prNumber: string;
|
||||||
@@ -87,6 +86,14 @@ type PullRequestEvent = {
|
|||||||
baseBranch?: string;
|
baseBranch?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PullRequestEvent = PullRequestBaseEvent & {
|
||||||
|
eventName: "pull_request";
|
||||||
|
};
|
||||||
|
|
||||||
|
type PullRequestTargetEvent = PullRequestBaseEvent & {
|
||||||
|
eventName: "pull_request_target";
|
||||||
|
};
|
||||||
|
|
||||||
// Union type for all possible event types
|
// Union type for all possible event types
|
||||||
export type EventData =
|
export type EventData =
|
||||||
| PullRequestReviewCommentEvent
|
| PullRequestReviewCommentEvent
|
||||||
@@ -96,7 +103,8 @@ export type EventData =
|
|||||||
| IssueOpenedEvent
|
| IssueOpenedEvent
|
||||||
| IssueAssignedEvent
|
| IssueAssignedEvent
|
||||||
| IssueLabeledEvent
|
| IssueLabeledEvent
|
||||||
| PullRequestEvent;
|
| PullRequestEvent
|
||||||
|
| PullRequestTargetEvent;
|
||||||
|
|
||||||
// Combined type with separate eventData field
|
// Combined type with separate eventData field
|
||||||
export type PreparedContext = CommonFields & {
|
export type PreparedContext = CommonFields & {
|
||||||
|
|||||||
@@ -174,7 +174,8 @@ export function parseGitHubContext(): GitHubContext {
|
|||||||
isPR: Boolean(payload.issue.pull_request),
|
isPR: Boolean(payload.issue.pull_request),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "pull_request": {
|
case "pull_request":
|
||||||
|
case "pull_request_target": {
|
||||||
const payload = context.payload as PullRequestEvent;
|
const payload = context.payload as PullRequestEvent;
|
||||||
return {
|
return {
|
||||||
...commonFields,
|
...commonFields,
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ export async function prepareMcpConfig(
|
|||||||
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||||
"-e",
|
"-e",
|
||||||
"GITHUB_HOST",
|
"GITHUB_HOST",
|
||||||
"ghcr.io/github/github-mcp-server:sha-efef8ae", // https://github.com/github/github-mcp-server/releases/tag/v0.9.0
|
"ghcr.io/github/github-mcp-server:sha-23fa0dd", // https://github.com/github/github-mcp-server/releases/tag/v0.17.1
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
GITHUB_PERSONAL_ACCESS_TOKEN: githubToken,
|
GITHUB_PERSONAL_ACCESS_TOKEN: githubToken,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
export function parseAllowedTools(claudeArgs: string): string[] {
|
export function parseAllowedTools(claudeArgs: string): string[] {
|
||||||
// Match --allowedTools followed by the value
|
// Match --allowedTools or --allowed-tools followed by the value
|
||||||
// Handle both quoted and unquoted values
|
// Handle both quoted and unquoted values
|
||||||
const patterns = [
|
const patterns = [
|
||||||
/--allowedTools\s+"([^"]+)"/, // Double quoted
|
/--(?:allowedTools|allowed-tools)\s+"([^"]+)"/, // Double quoted
|
||||||
/--allowedTools\s+'([^']+)'/, // Single quoted
|
/--(?:allowedTools|allowed-tools)\s+'([^']+)'/, // Single quoted
|
||||||
/--allowedTools\s+([^\s]+)/, // Unquoted
|
/--(?:allowedTools|allowed-tools)\s+([^\s]+)/, // Unquoted
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
|
|||||||
@@ -68,4 +68,20 @@ describe("parseAllowedTools", () => {
|
|||||||
"mcp__github_comment__update",
|
"mcp__github_comment__update",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("parses kebab-case --allowed-tools", () => {
|
||||||
|
const args = "--allowed-tools mcp__github__*,mcp__github_comment__*";
|
||||||
|
expect(parseAllowedTools(args)).toEqual([
|
||||||
|
"mcp__github__*",
|
||||||
|
"mcp__github_comment__*",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("parses quoted kebab-case --allowed-tools", () => {
|
||||||
|
const args = '--allowed-tools "mcp__github__*,mcp__github_comment__*"';
|
||||||
|
expect(parseAllowedTools(args)).toEqual([
|
||||||
|
"mcp__github__*",
|
||||||
|
"mcp__github_comment__*",
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
504
test/pull-request-target.test.ts
Normal file
504
test/pull-request-target.test.ts
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import { describe, test, expect } from "bun:test";
|
||||||
|
import {
|
||||||
|
getEventTypeAndContext,
|
||||||
|
generatePrompt,
|
||||||
|
generateDefaultPrompt,
|
||||||
|
} from "../src/create-prompt";
|
||||||
|
import type { PreparedContext } from "../src/create-prompt";
|
||||||
|
import type { Mode } from "../src/modes/types";
|
||||||
|
|
||||||
|
describe("pull_request_target event support", () => {
|
||||||
|
// Mock tag mode for testing
|
||||||
|
const mockTagMode: Mode = {
|
||||||
|
name: "tag",
|
||||||
|
description: "Tag mode",
|
||||||
|
shouldTrigger: () => true,
|
||||||
|
prepareContext: (context) => ({ mode: "tag", githubContext: context }),
|
||||||
|
getAllowedTools: () => [],
|
||||||
|
getDisallowedTools: () => [],
|
||||||
|
shouldCreateTrackingComment: () => true,
|
||||||
|
generatePrompt: (context, githubData, useCommitSigning) =>
|
||||||
|
generateDefaultPrompt(context, githubData, useCommitSigning),
|
||||||
|
prepare: async () => ({
|
||||||
|
commentId: 123,
|
||||||
|
branchInfo: {
|
||||||
|
baseBranch: "main",
|
||||||
|
currentBranch: "main",
|
||||||
|
claudeBranch: undefined,
|
||||||
|
},
|
||||||
|
mcpConfig: "{}",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockGitHubData = {
|
||||||
|
contextData: {
|
||||||
|
title: "External PR via pull_request_target",
|
||||||
|
body: "This PR comes from a forked repository",
|
||||||
|
author: { login: "external-contributor" },
|
||||||
|
state: "OPEN",
|
||||||
|
createdAt: "2023-01-01T00:00:00Z",
|
||||||
|
additions: 25,
|
||||||
|
deletions: 3,
|
||||||
|
baseRefName: "main",
|
||||||
|
headRefName: "feature-branch",
|
||||||
|
headRefOid: "abc123",
|
||||||
|
commits: {
|
||||||
|
totalCount: 2,
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
commit: {
|
||||||
|
oid: "commit1",
|
||||||
|
message: "Initial feature implementation",
|
||||||
|
author: {
|
||||||
|
name: "External Dev",
|
||||||
|
email: "external@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
commit: {
|
||||||
|
oid: "commit2",
|
||||||
|
message: "Fix typos and formatting",
|
||||||
|
author: {
|
||||||
|
name: "External Dev",
|
||||||
|
email: "external@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
path: "src/feature.ts",
|
||||||
|
additions: 20,
|
||||||
|
deletions: 2,
|
||||||
|
changeType: "MODIFIED",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "tests/feature.test.ts",
|
||||||
|
additions: 5,
|
||||||
|
deletions: 1,
|
||||||
|
changeType: "ADDED",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
comments: { nodes: [] },
|
||||||
|
reviews: { nodes: [] },
|
||||||
|
},
|
||||||
|
comments: [],
|
||||||
|
changedFiles: [],
|
||||||
|
changedFilesWithSHA: [
|
||||||
|
{
|
||||||
|
path: "src/feature.ts",
|
||||||
|
additions: 20,
|
||||||
|
deletions: 2,
|
||||||
|
changeType: "MODIFIED",
|
||||||
|
sha: "abc123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "tests/feature.test.ts",
|
||||||
|
additions: 5,
|
||||||
|
deletions: 1,
|
||||||
|
changeType: "ADDED",
|
||||||
|
sha: "abc123",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
reviewData: { nodes: [] },
|
||||||
|
imageUrlMap: new Map<string, string>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("prompt generation for pull_request_target", () => {
|
||||||
|
test("should generate correct prompt for pull_request_target event", () => {
|
||||||
|
const envVars: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
eventAction: "opened",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "123",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = generatePrompt(
|
||||||
|
envVars,
|
||||||
|
mockGitHubData,
|
||||||
|
false,
|
||||||
|
mockTagMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should contain pull request event type and metadata
|
||||||
|
expect(prompt).toContain("<event_type>PULL_REQUEST</event_type>");
|
||||||
|
expect(prompt).toContain("<is_pr>true</is_pr>");
|
||||||
|
expect(prompt).toContain("<pr_number>123</pr_number>");
|
||||||
|
expect(prompt).toContain(
|
||||||
|
"<trigger_context>pull request opened</trigger_context>",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should contain PR-specific information
|
||||||
|
expect(prompt).toContain(
|
||||||
|
"- src/feature.ts (MODIFIED) +20/-2 SHA: abc123",
|
||||||
|
);
|
||||||
|
expect(prompt).toContain(
|
||||||
|
"- tests/feature.test.ts (ADDED) +5/-1 SHA: abc123",
|
||||||
|
);
|
||||||
|
expect(prompt).toContain("external-contributor");
|
||||||
|
expect(prompt).toContain("<repository>owner/repo</repository>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle pull_request_target with commit signing disabled", () => {
|
||||||
|
const envVars: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
eventAction: "synchronize",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "456",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = generatePrompt(
|
||||||
|
envVars,
|
||||||
|
mockGitHubData,
|
||||||
|
false,
|
||||||
|
mockTagMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should include git commands for non-commit-signing mode
|
||||||
|
expect(prompt).toContain("git push");
|
||||||
|
expect(prompt).toContain(
|
||||||
|
"Always push to the existing branch when triggered on a PR",
|
||||||
|
);
|
||||||
|
expect(prompt).toContain("mcp__github_comment__update_claude_comment");
|
||||||
|
|
||||||
|
// Should not include commit signing tools
|
||||||
|
expect(prompt).not.toContain("mcp__github_file_ops__commit_files");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle pull_request_target with commit signing enabled", () => {
|
||||||
|
const envVars: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
eventAction: "synchronize",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "456",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = generatePrompt(envVars, mockGitHubData, true, mockTagMode);
|
||||||
|
|
||||||
|
// Should include commit signing tools
|
||||||
|
expect(prompt).toContain("mcp__github_file_ops__commit_files");
|
||||||
|
expect(prompt).toContain("mcp__github_file_ops__delete_files");
|
||||||
|
expect(prompt).toContain("mcp__github_comment__update_claude_comment");
|
||||||
|
|
||||||
|
// Should not include git command instructions
|
||||||
|
expect(prompt).not.toContain("Use git commands via the Bash tool");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should treat pull_request_target same as pull_request in prompt generation", () => {
|
||||||
|
const baseContext: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
eventAction: "opened",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "123",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate prompt for pull_request
|
||||||
|
const pullRequestContext: PreparedContext = {
|
||||||
|
...baseContext,
|
||||||
|
eventData: {
|
||||||
|
...baseContext.eventData,
|
||||||
|
eventName: "pull_request",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "123",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate prompt for pull_request_target
|
||||||
|
const pullRequestTargetContext: PreparedContext = {
|
||||||
|
...baseContext,
|
||||||
|
eventData: {
|
||||||
|
...baseContext.eventData,
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "123",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pullRequestPrompt = generatePrompt(
|
||||||
|
pullRequestContext,
|
||||||
|
mockGitHubData,
|
||||||
|
false,
|
||||||
|
mockTagMode,
|
||||||
|
);
|
||||||
|
const pullRequestTargetPrompt = generatePrompt(
|
||||||
|
pullRequestTargetContext,
|
||||||
|
mockGitHubData,
|
||||||
|
false,
|
||||||
|
mockTagMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Both should have the same event type and structure
|
||||||
|
expect(pullRequestPrompt).toContain(
|
||||||
|
"<event_type>PULL_REQUEST</event_type>",
|
||||||
|
);
|
||||||
|
expect(pullRequestTargetPrompt).toContain(
|
||||||
|
"<event_type>PULL_REQUEST</event_type>",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(pullRequestPrompt).toContain(
|
||||||
|
"<trigger_context>pull request opened</trigger_context>",
|
||||||
|
);
|
||||||
|
expect(pullRequestTargetPrompt).toContain(
|
||||||
|
"<trigger_context>pull request opened</trigger_context>",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Both should contain PR-specific instructions
|
||||||
|
expect(pullRequestPrompt).toContain(
|
||||||
|
"Always push to the existing branch when triggered on a PR",
|
||||||
|
);
|
||||||
|
expect(pullRequestTargetPrompt).toContain(
|
||||||
|
"Always push to the existing branch when triggered on a PR",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle pull_request_target in agent mode with custom prompt", () => {
|
||||||
|
const envVars: PreparedContext = {
|
||||||
|
repository: "test/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
prompt: "Review this pull_request_target PR for security issues",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
eventAction: "opened",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "789",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use agent mode which passes through the prompt as-is
|
||||||
|
const mockAgentMode: Mode = {
|
||||||
|
name: "agent",
|
||||||
|
description: "Agent mode",
|
||||||
|
shouldTrigger: () => true,
|
||||||
|
prepareContext: (context) => ({
|
||||||
|
mode: "agent",
|
||||||
|
githubContext: context,
|
||||||
|
}),
|
||||||
|
getAllowedTools: () => [],
|
||||||
|
getDisallowedTools: () => [],
|
||||||
|
shouldCreateTrackingComment: () => true,
|
||||||
|
generatePrompt: (context) => context.prompt || "default prompt",
|
||||||
|
prepare: async () => ({
|
||||||
|
commentId: 123,
|
||||||
|
branchInfo: {
|
||||||
|
baseBranch: "main",
|
||||||
|
currentBranch: "main",
|
||||||
|
claudeBranch: undefined,
|
||||||
|
},
|
||||||
|
mcpConfig: "{}",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = generatePrompt(
|
||||||
|
envVars,
|
||||||
|
mockGitHubData,
|
||||||
|
false,
|
||||||
|
mockAgentMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(prompt).toBe(
|
||||||
|
"Review this pull_request_target PR for security issues",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle pull_request_target with no custom prompt", () => {
|
||||||
|
const envVars: PreparedContext = {
|
||||||
|
repository: "test/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
eventAction: "synchronize",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "456",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const prompt = generatePrompt(
|
||||||
|
envVars,
|
||||||
|
mockGitHubData,
|
||||||
|
false,
|
||||||
|
mockTagMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should generate default prompt structure
|
||||||
|
expect(prompt).toContain("<event_type>PULL_REQUEST</event_type>");
|
||||||
|
expect(prompt).toContain("<pr_number>456</pr_number>");
|
||||||
|
expect(prompt).toContain(
|
||||||
|
"Always push to the existing branch when triggered on a PR",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("pull_request_target vs pull_request behavior consistency", () => {
|
||||||
|
test("should produce identical event processing for both event types", () => {
|
||||||
|
const baseEventData = {
|
||||||
|
eventAction: "opened",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "100",
|
||||||
|
};
|
||||||
|
|
||||||
|
const pullRequestEvent: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
...baseEventData,
|
||||||
|
eventName: "pull_request",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "100",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pullRequestTargetEvent: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
...baseEventData,
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "100",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Both should have identical event type detection
|
||||||
|
const prResult = getEventTypeAndContext(pullRequestEvent);
|
||||||
|
const prtResult = getEventTypeAndContext(pullRequestTargetEvent);
|
||||||
|
|
||||||
|
expect(prResult.eventType).toBe(prtResult.eventType);
|
||||||
|
expect(prResult.triggerContext).toBe(prtResult.triggerContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle edge cases in pull_request_target events", () => {
|
||||||
|
// Test with minimal event data
|
||||||
|
const minimalContext: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getEventTypeAndContext(minimalContext);
|
||||||
|
expect(result.eventType).toBe("PULL_REQUEST");
|
||||||
|
expect(result.triggerContext).toBe("pull request event");
|
||||||
|
|
||||||
|
// Should not throw when generating prompt
|
||||||
|
expect(() => {
|
||||||
|
generatePrompt(minimalContext, mockGitHubData, false, mockTagMode);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle all valid pull_request_target actions", () => {
|
||||||
|
const actions = ["opened", "synchronize", "reopened", "closed", "edited"];
|
||||||
|
|
||||||
|
actions.forEach((action) => {
|
||||||
|
const context: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
eventAction: action,
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getEventTypeAndContext(context);
|
||||||
|
expect(result.eventType).toBe("PULL_REQUEST");
|
||||||
|
expect(result.triggerContext).toBe(`pull request ${action}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("security considerations for pull_request_target", () => {
|
||||||
|
test("should maintain same prompt structure regardless of event source", () => {
|
||||||
|
// Test that external PRs don't get different treatment in prompts
|
||||||
|
const internalPR: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request",
|
||||||
|
eventAction: "opened",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const externalPR: PreparedContext = {
|
||||||
|
repository: "owner/repo",
|
||||||
|
claudeCommentId: "12345",
|
||||||
|
triggerPhrase: "@claude",
|
||||||
|
eventData: {
|
||||||
|
eventName: "pull_request_target",
|
||||||
|
eventAction: "opened",
|
||||||
|
isPR: true,
|
||||||
|
prNumber: "1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const internalPrompt = generatePrompt(
|
||||||
|
internalPR,
|
||||||
|
mockGitHubData,
|
||||||
|
false,
|
||||||
|
mockTagMode,
|
||||||
|
);
|
||||||
|
const externalPrompt = generatePrompt(
|
||||||
|
externalPR,
|
||||||
|
mockGitHubData,
|
||||||
|
false,
|
||||||
|
mockTagMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should have same tool access patterns
|
||||||
|
expect(
|
||||||
|
internalPrompt.includes("mcp__github_comment__update_claude_comment"),
|
||||||
|
).toBe(
|
||||||
|
externalPrompt.includes("mcp__github_comment__update_claude_comment"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should have same branch handling instructions
|
||||||
|
expect(
|
||||||
|
internalPrompt.includes(
|
||||||
|
"Always push to the existing branch when triggered on a PR",
|
||||||
|
),
|
||||||
|
).toBe(
|
||||||
|
externalPrompt.includes(
|
||||||
|
"Always push to the existing branch when triggered on a PR",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user