mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-24 07:24:12 +08:00
Compare commits
18 Commits
ashwin/bra
...
v1.0.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90d189f3ab | ||
|
|
9c09b26b2d | ||
|
|
2086c977a5 | ||
|
|
851ef5b84e | ||
|
|
1ce8153c18 | ||
|
|
00391ab25e | ||
|
|
426380f01b | ||
|
|
77f51d2905 | ||
|
|
7e5b42b197 | ||
|
|
1b7c7a77d3 | ||
|
|
bd70a3ef2b | ||
|
|
f4954b5256 | ||
|
|
93f8ab56c2 | ||
|
|
93028b410e | ||
|
|
838d4d9d25 | ||
|
|
7ed3b616d5 | ||
|
|
09ea2f00e1 | ||
|
|
455b943dd7 |
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 }}
|
|
||||||
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.2
|
||||||
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 || '' }}
|
||||||
|
|||||||
@@ -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.2
|
||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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