mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 15:04:13 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4d7974604 | ||
|
|
8fcb8e16b8 | ||
|
|
06b3126baf | ||
|
|
bf2400d475 | ||
|
|
4e2cfbac36 | ||
|
|
018533dc9a | ||
|
|
a9d9ad3612 | ||
|
|
4824494f4d | ||
|
|
c09fc691c5 | ||
|
|
b3c6de94ea | ||
|
|
b92e56a96b | ||
|
|
b6868bfc27 | ||
|
|
0f9a2c4dc3 |
1
.github/workflows/claude-review.yml
vendored
1
.github/workflows/claude-review.yml
vendored
@@ -26,6 +26,7 @@ jobs:
|
||||
- Potential bugs or issues
|
||||
- Suggestions for improvements
|
||||
- Overall architecture and design decisions
|
||||
- Documentation consistency: Verify that README.md and other documentation files are updated to reflect any code changes (especially new inputs, features, or configuration options)
|
||||
|
||||
Be constructive and specific in your feedback. Give inline comments where applicable.
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
257
README.md
257
README.md
@@ -35,6 +35,86 @@ This command will guide you through setting up the GitHub app and required secre
|
||||
- Or `CLAUDE_CODE_OAUTH_TOKEN` for OAuth token authentication (Pro and Max users can generate this by running `claude setup-token` locally)
|
||||
3. Copy the workflow file from [`examples/claude.yml`](./examples/claude.yml) into your repository's `.github/workflows/`
|
||||
|
||||
### Using a Custom GitHub App
|
||||
|
||||
If you prefer not to install the official Claude app, you can create your own GitHub App to use with this action. This gives you complete control over permissions and access.
|
||||
|
||||
**When you may want to use a custom GitHub App:**
|
||||
|
||||
- You need more restrictive permissions than the official app
|
||||
- Organization policies prevent installing third-party apps
|
||||
- You're using AWS Bedrock or Google Vertex AI
|
||||
|
||||
**Steps to create and use a custom GitHub App:**
|
||||
|
||||
1. **Create a new GitHub App:**
|
||||
|
||||
- Go to https://github.com/settings/apps (for personal apps) or your organization's settings
|
||||
- Click "New GitHub App"
|
||||
- Configure the app with these minimum permissions:
|
||||
- **Repository permissions:**
|
||||
- Contents: Read & Write
|
||||
- Issues: Read & Write
|
||||
- Pull requests: Read & Write
|
||||
- **Account permissions:** None required
|
||||
- Set "Where can this GitHub App be installed?" to your preference
|
||||
- Create the app
|
||||
|
||||
2. **Generate and download a private key:**
|
||||
|
||||
- After creating the app, scroll down to "Private keys"
|
||||
- Click "Generate a private key"
|
||||
- Download the `.pem` file (keep this secure!)
|
||||
|
||||
3. **Install the app on your repository:**
|
||||
|
||||
- Go to the app's settings page
|
||||
- Click "Install App"
|
||||
- Select the repositories where you want to use Claude
|
||||
|
||||
4. **Add the app credentials to your repository secrets:**
|
||||
|
||||
- Go to your repository's Settings → Secrets and variables → Actions
|
||||
- Add these secrets:
|
||||
- `APP_ID`: Your GitHub App's ID (found in the app settings)
|
||||
- `APP_PRIVATE_KEY`: The contents of the downloaded `.pem` file
|
||||
|
||||
5. **Update your workflow to use the custom app:**
|
||||
|
||||
```yaml
|
||||
name: Claude with Custom App
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
# ... other triggers
|
||||
|
||||
jobs:
|
||||
claude-response:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Generate a token from your custom app
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
# Use Claude with your custom app's token
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ steps.app-token.outputs.token }}
|
||||
# ... other configuration
|
||||
```
|
||||
|
||||
**Important notes:**
|
||||
|
||||
- The custom app must have read/write permissions for Issues, Pull Requests, and Contents
|
||||
- Your app's token will have the exact permissions you configured, nothing more
|
||||
|
||||
For more information on creating GitHub Apps, see the [GitHub documentation](https://docs.github.com/en/apps/creating-github-apps).
|
||||
|
||||
## 📚 FAQ
|
||||
|
||||
Having issues or questions? Check out our [Frequently Asked Questions](./FAQ.md) for solutions to common problems and detailed explanations of Claude's capabilities and limitations.
|
||||
@@ -85,31 +165,34 @@ jobs:
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
|
||||
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
|
||||
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No\* | - |
|
||||
| `direct_prompt` | Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) | No | - |
|
||||
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
|
||||
| `max_turns` | Maximum number of conversation turns Claude can take (limits back-and-forth exchanges) | No | - |
|
||||
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
|
||||
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |
|
||||
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
|
||||
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
|
||||
| `fallback_model` | Enable automatic fallback to specified model when primary model is unavailable | No | - |
|
||||
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
|
||||
| `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` |
|
||||
| `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" |
|
||||
| `disallowed_tools` | Tools that Claude should never use | No | "" |
|
||||
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
|
||||
| `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 | - |
|
||||
| `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` |
|
||||
| `branch_prefix` | The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format) | No | `claude/` |
|
||||
| `claude_env` | Custom environment variables to pass to Claude Code execution (YAML format) | No | "" |
|
||||
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
|
||||
| Input | Description | Required | Default |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
|
||||
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
|
||||
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No\* | - |
|
||||
| `direct_prompt` | Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) | No | - |
|
||||
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
|
||||
| `max_turns` | Maximum number of conversation turns Claude can take (limits back-and-forth exchanges) | No | - |
|
||||
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
|
||||
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |
|
||||
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
|
||||
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
|
||||
| `fallback_model` | Enable automatic fallback to specified model when primary model is unavailable | No | - |
|
||||
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
|
||||
| `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` |
|
||||
| `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" |
|
||||
| `disallowed_tools` | Tools that Claude should never use | No | "" |
|
||||
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
|
||||
| `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 | - |
|
||||
| `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` |
|
||||
| `branch_prefix` | The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format) | No | `claude/` |
|
||||
| `claude_env` | Custom environment variables to pass to Claude Code execution (YAML format) | No | "" |
|
||||
| `settings` | Claude Code settings as JSON string or path to settings JSON file | No | "" |
|
||||
| `additional_permissions` | Additional permissions to enable. Currently supports 'actions: read' for viewing workflow results | No | "" |
|
||||
| `experimental_allowed_domains` | Restrict network access to these domains only (newline-separated). | No | "" |
|
||||
| `use_commit_signing` | Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands | No | `false` |
|
||||
|
||||
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)
|
||||
|
||||
@@ -491,6 +574,130 @@ Use a specific Claude model:
|
||||
# ... other inputs
|
||||
```
|
||||
|
||||
### Network Restrictions
|
||||
|
||||
For enhanced security, you can restrict Claude's network access to specific domains only. This feature is particularly useful for:
|
||||
|
||||
- Enterprise environments with strict security policies
|
||||
- Preventing access to external services
|
||||
- Limiting Claude to only your internal APIs and services
|
||||
|
||||
When `experimental_allowed_domains` is set, Claude can only access the domains you explicitly list. You'll need to include the appropriate provider domains based on your authentication method.
|
||||
|
||||
#### Provider-Specific Examples
|
||||
|
||||
##### If using Anthropic API or subscription
|
||||
|
||||
```yaml
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
# Or: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
experimental_allowed_domains: |
|
||||
.anthropic.com
|
||||
```
|
||||
|
||||
##### If using AWS Bedrock
|
||||
|
||||
```yaml
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
use_bedrock: "true"
|
||||
experimental_allowed_domains: |
|
||||
bedrock.*.amazonaws.com
|
||||
bedrock-runtime.*.amazonaws.com
|
||||
```
|
||||
|
||||
##### If using Google Vertex AI
|
||||
|
||||
```yaml
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
use_vertex: "true"
|
||||
experimental_allowed_domains: |
|
||||
*.googleapis.com
|
||||
vertexai.googleapis.com
|
||||
```
|
||||
|
||||
#### Common GitHub Domains
|
||||
|
||||
In addition to your provider domains, you may need to include GitHub-related domains. For GitHub.com users, common domains include:
|
||||
|
||||
```yaml
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
experimental_allowed_domains: |
|
||||
.anthropic.com # For Anthropic API
|
||||
.github.com
|
||||
.githubusercontent.com
|
||||
ghcr.io
|
||||
.blob.core.windows.net
|
||||
```
|
||||
|
||||
For GitHub Enterprise users, replace the GitHub.com domains above with your enterprise domains (e.g., `.github.company.com`, `packages.company.com`, etc.).
|
||||
|
||||
To determine which domains your workflow needs, you can temporarily run without restrictions and monitor the network requests, or check your GitHub Enterprise configuration for the specific services you use.
|
||||
|
||||
### Claude Code Settings
|
||||
|
||||
You can provide Claude Code settings to customize behavior such as model selection, environment variables, permissions, and hooks. Settings can be provided either as a JSON string or a path to a settings file.
|
||||
|
||||
#### Option 1: Settings File
|
||||
|
||||
```yaml
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
settings: "path/to/settings.json"
|
||||
# ... other inputs
|
||||
```
|
||||
|
||||
#### Option 2: Inline Settings
|
||||
|
||||
```yaml
|
||||
- uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
settings: |
|
||||
{
|
||||
"model": "claude-opus-4-20250514",
|
||||
"env": {
|
||||
"DEBUG": "true",
|
||||
"API_URL": "https://api.example.com"
|
||||
},
|
||||
"permissions": {
|
||||
"allow": ["Bash", "Read"],
|
||||
"deny": ["WebFetch"]
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse": [{
|
||||
"matcher": "Bash",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "echo Running bash command..."
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
# ... other inputs
|
||||
```
|
||||
|
||||
The settings support all Claude Code settings options including:
|
||||
|
||||
- `model`: Override the default model
|
||||
- `env`: Environment variables for the session
|
||||
- `permissions`: Tool usage permissions
|
||||
- `hooks`: Pre/post tool execution hooks
|
||||
- And more...
|
||||
|
||||
For a complete list of available settings and their descriptions, see the [Claude Code settings documentation](https://docs.anthropic.com/en/docs/claude-code/settings).
|
||||
|
||||
**Notes**:
|
||||
|
||||
- The `enableAllProjectMcpServers` setting is always set to `true` by this action to ensure MCP servers work correctly.
|
||||
- If both the `model` input parameter and a `model` in settings are provided, the `model` input parameter takes precedence.
|
||||
- The `allowed_tools` and `disallowed_tools` input parameters take precedence over `permissions` in settings.
|
||||
- In a future version, we may deprecate individual input parameters in favor of using the settings file for all configuration.
|
||||
|
||||
## Cloud Providers
|
||||
|
||||
You can authenticate with Claude using any of these three methods:
|
||||
|
||||
46
action.yml
46
action.yml
@@ -60,6 +60,10 @@ inputs:
|
||||
description: "Custom environment variables to pass to Claude Code execution (YAML format)"
|
||||
required: false
|
||||
default: ""
|
||||
settings:
|
||||
description: "Claude Code settings as JSON string or path to settings JSON file"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
# Auth configuration
|
||||
anthropic_api_key:
|
||||
@@ -96,11 +100,18 @@ inputs:
|
||||
description: "Enable commit signing using GitHub's commit signature verification. When false, Claude uses standard git commands"
|
||||
required: false
|
||||
default: "false"
|
||||
experimental_allowed_domains:
|
||||
description: "Restrict network access to these domains only (newline-separated). If not set, no restrictions are applied. Provider domains are auto-detected."
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
outputs:
|
||||
execution_file:
|
||||
description: "Path to the Claude Code execution output file"
|
||||
value: ${{ steps.claude-code.outputs.execution_file }}
|
||||
branch_name:
|
||||
description: "The branch created by Claude Code for this execution"
|
||||
value: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -139,10 +150,42 @@ runs:
|
||||
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
|
||||
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
|
||||
|
||||
- name: Setup Network Restrictions
|
||||
if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != ''
|
||||
shell: bash
|
||||
run: |
|
||||
# Install and configure Squid proxy
|
||||
sudo apt-get update && sudo apt-get install -y squid
|
||||
|
||||
echo "${{ inputs.experimental_allowed_domains }}" > $RUNNER_TEMP/whitelist.txt
|
||||
|
||||
# Configure Squid
|
||||
sudo tee /etc/squid/squid.conf << EOF
|
||||
http_port 127.0.0.1:3128
|
||||
acl whitelist dstdomain "$RUNNER_TEMP/whitelist.txt"
|
||||
acl localhost src 127.0.0.1/32
|
||||
http_access allow localhost whitelist
|
||||
http_access deny all
|
||||
cache deny all
|
||||
EOF
|
||||
|
||||
# Stop any existing squid instance and start with our config
|
||||
sudo squid -k shutdown || true
|
||||
sleep 2
|
||||
sudo rm -f /run/squid.pid
|
||||
sudo squid -N -d 1 &
|
||||
sleep 5
|
||||
|
||||
# Set proxy environment variables
|
||||
echo "http_proxy=http://127.0.0.1:3128" >> $GITHUB_ENV
|
||||
echo "https_proxy=http://127.0.0.1:3128" >> $GITHUB_ENV
|
||||
echo "HTTP_PROXY=http://127.0.0.1:3128" >> $GITHUB_ENV
|
||||
echo "HTTPS_PROXY=http://127.0.0.1:3128" >> $GITHUB_ENV
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude-code
|
||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||
uses: anthropics/claude-code-base-action@0f7a229cb06f840f77f49df0b711ee0060868c2c # v0.0.33
|
||||
uses: anthropics/claude-code-base-action@03e2a2d6923a9187c8e93b04ef2f8dae3219d0b1 # v0.0.36
|
||||
with:
|
||||
prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
||||
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
||||
@@ -157,6 +200,7 @@ runs:
|
||||
anthropic_api_key: ${{ inputs.anthropic_api_key }}
|
||||
claude_code_oauth_token: ${{ inputs.claude_code_oauth_token }}
|
||||
claude_env: ${{ inputs.claude_env }}
|
||||
settings: ${{ inputs.settings }}
|
||||
env:
|
||||
# Model configuration
|
||||
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}
|
||||
|
||||
@@ -36,3 +36,12 @@ jobs:
|
||||
# Or use OAuth token instead:
|
||||
# claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
timeout_minutes: "60"
|
||||
# Optional: Restrict network access to specific domains only
|
||||
# experimental_allowed_domains: |
|
||||
# .anthropic.com
|
||||
# .github.com
|
||||
# api.github.com
|
||||
# .githubusercontent.com
|
||||
# bun.sh
|
||||
# registry.npmjs.org
|
||||
# .blob.core.windows.net
|
||||
|
||||
@@ -91,7 +91,8 @@ async function run() {
|
||||
githubToken,
|
||||
owner: context.repository.owner,
|
||||
repo: context.repository.repo,
|
||||
branch: branchInfo.currentBranch,
|
||||
branch: branchInfo.claudeBranch || branchInfo.currentBranch,
|
||||
baseBranch: branchInfo.baseBranch,
|
||||
additionalMcpConfig,
|
||||
claudeCommentId: commentId.toString(),
|
||||
allowedTools: context.inputs.allowedTools,
|
||||
|
||||
@@ -86,14 +86,18 @@ export async function setupBranch(
|
||||
|
||||
// Generate branch name for either an issue or closed/merged PR
|
||||
const entityType = isPR ? "pr" : "issue";
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
.replace(/[:-]/g, "")
|
||||
.replace(/\.\d{3}Z/, "")
|
||||
.split("T")
|
||||
.join("_");
|
||||
|
||||
const newBranch = `${branchPrefix}${entityType}-${entityNumber}-${timestamp}`;
|
||||
// Create Kubernetes-compatible timestamp: lowercase, hyphens only, shorter format
|
||||
const now = new Date();
|
||||
const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
||||
|
||||
// Ensure branch name is Kubernetes-compatible:
|
||||
// - Lowercase only
|
||||
// - Alphanumeric with hyphens
|
||||
// - No underscores
|
||||
// - Max 50 chars (to allow for prefixes)
|
||||
const branchName = `${branchPrefix}${entityType}-${entityNumber}-${timestamp}`;
|
||||
const newBranch = branchName.toLowerCase().substring(0, 50);
|
||||
|
||||
try {
|
||||
// Get the SHA of the source branch to verify it exists
|
||||
|
||||
@@ -21,6 +21,13 @@ export async function configureGitAuth(
|
||||
) {
|
||||
console.log("Configuring git authentication for non-signing mode");
|
||||
|
||||
// Determine the noreply email domain based on GITHUB_SERVER_URL
|
||||
const serverUrl = new URL(GITHUB_SERVER_URL);
|
||||
const noreplyDomain =
|
||||
serverUrl.hostname === "github.com"
|
||||
? "users.noreply.github.com"
|
||||
: `users.noreply.${serverUrl.hostname}`;
|
||||
|
||||
// Configure git user based on the comment creator
|
||||
console.log("Configuring git user...");
|
||||
if (user) {
|
||||
@@ -28,12 +35,12 @@ export async function configureGitAuth(
|
||||
const botId = user.id;
|
||||
console.log(`Setting git user as ${botName}...`);
|
||||
await $`git config user.name "${botName}"`;
|
||||
await $`git config user.email "${botId}+${botName}@users.noreply.github.com"`;
|
||||
await $`git config user.email "${botId}+${botName}@${noreplyDomain}"`;
|
||||
console.log(`✓ Set git user as ${botName}`);
|
||||
} else {
|
||||
console.log("No user data in comment, using default bot user");
|
||||
await $`git config user.name "github-actions[bot]"`;
|
||||
await $`git config user.email "41898282+github-actions[bot]@users.noreply.github.com"`;
|
||||
await $`git config user.email "41898282+github-actions[bot]@${noreplyDomain}"`;
|
||||
}
|
||||
|
||||
// Remove the authorization header that actions/checkout sets
|
||||
@@ -47,7 +54,6 @@ export async function configureGitAuth(
|
||||
|
||||
// Update the remote URL to include the token for authentication
|
||||
console.log("Updating remote URL with authentication...");
|
||||
const serverUrl = new URL(GITHUB_SERVER_URL);
|
||||
const remoteUrl = `https://x-access-token:${githubToken}@${serverUrl.host}/${context.repository.owner}/${context.repository.repo}.git`;
|
||||
await $`git remote set-url origin ${remoteUrl}`;
|
||||
console.log("✓ Updated remote URL with authentication token");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
import { GITHUB_API_URL } from "../github/api/config";
|
||||
import { mkdir, writeFile } from "fs/promises";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
|
||||
@@ -54,6 +55,7 @@ server.tool(
|
||||
try {
|
||||
const client = new Octokit({
|
||||
auth: GITHUB_TOKEN,
|
||||
baseUrl: GITHUB_API_URL,
|
||||
});
|
||||
|
||||
// Get the PR to find the head SHA
|
||||
@@ -142,6 +144,7 @@ server.tool(
|
||||
try {
|
||||
const client = new Octokit({
|
||||
auth: GITHUB_TOKEN,
|
||||
baseUrl: GITHUB_API_URL,
|
||||
});
|
||||
|
||||
// Get jobs for this workflow run
|
||||
@@ -209,6 +212,7 @@ server.tool(
|
||||
try {
|
||||
const client = new Octokit({
|
||||
auth: GITHUB_TOKEN,
|
||||
baseUrl: GITHUB_API_URL,
|
||||
});
|
||||
|
||||
const response = await client.actions.downloadJobLogsForWorkflowRun({
|
||||
|
||||
@@ -78,11 +78,7 @@ async function getOrCreateBranchRef(
|
||||
throw new Error(`Failed to get branch reference: ${refResponse.status}`);
|
||||
}
|
||||
|
||||
// Branch doesn't exist, need to create it
|
||||
console.log(`Branch ${branch} does not exist, creating it...`);
|
||||
|
||||
// Get base branch from environment or determine it
|
||||
const baseBranch = process.env.BASE_BRANCH || "main";
|
||||
const baseBranch = process.env.BASE_BRANCH!;
|
||||
|
||||
// Get the SHA of the base branch
|
||||
const baseRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${baseBranch}`;
|
||||
@@ -139,7 +135,7 @@ async function getOrCreateBranchRef(
|
||||
baseSha = baseRefData.object.sha;
|
||||
}
|
||||
|
||||
// Create the new branch
|
||||
// Create the new branch using the same pattern as octokit
|
||||
const createRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs`;
|
||||
const createRefResponse = await fetch(createRefUrl, {
|
||||
method: "POST",
|
||||
|
||||
@@ -8,6 +8,7 @@ type PrepareConfigParams = {
|
||||
owner: string;
|
||||
repo: string;
|
||||
branch: string;
|
||||
baseBranch: string;
|
||||
additionalMcpConfig?: string;
|
||||
claudeCommentId?: string;
|
||||
allowedTools: string[];
|
||||
@@ -20,7 +21,7 @@ async function checkActionsReadPermission(
|
||||
repo: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const client = new Octokit({ auth: token });
|
||||
const client = new Octokit({ auth: token, baseUrl: GITHUB_API_URL });
|
||||
|
||||
// Try to list workflow runs - this requires actions:read
|
||||
// We use per_page=1 to minimize the response size
|
||||
@@ -54,6 +55,7 @@ export async function prepareMcpConfig(
|
||||
owner,
|
||||
repo,
|
||||
branch,
|
||||
baseBranch,
|
||||
additionalMcpConfig,
|
||||
claudeCommentId,
|
||||
allowedTools,
|
||||
@@ -100,7 +102,7 @@ export async function prepareMcpConfig(
|
||||
REPO_OWNER: owner,
|
||||
REPO_NAME: repo,
|
||||
BRANCH_NAME: branch,
|
||||
BASE_BRANCH: process.env.BASE_BRANCH || "",
|
||||
BASE_BRANCH: baseBranch,
|
||||
REPO_DIR: process.env.GITHUB_WORKSPACE || process.cwd(),
|
||||
GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME || "",
|
||||
IS_PR: process.env.IS_PR || "false",
|
||||
|
||||
@@ -72,7 +72,7 @@ describe("checkAndCommitOrDeleteBranch", () => {
|
||||
mockOctokit,
|
||||
"owner",
|
||||
"repo",
|
||||
"claude/issue-123-20240101_123456",
|
||||
"claude/issue-123-20240101-1234",
|
||||
"main",
|
||||
true, // commit signing enabled
|
||||
);
|
||||
@@ -80,7 +80,7 @@ describe("checkAndCommitOrDeleteBranch", () => {
|
||||
expect(result.shouldDeleteBranch).toBe(true);
|
||||
expect(result.branchLink).toBe("");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
"Branch claude/issue-123-20240101_123456 has no commits from Claude, will delete it",
|
||||
"Branch claude/issue-123-20240101-1234 has no commits from Claude, will delete it",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -90,14 +90,14 @@ describe("checkAndCommitOrDeleteBranch", () => {
|
||||
mockOctokit,
|
||||
"owner",
|
||||
"repo",
|
||||
"claude/issue-123-20240101_123456",
|
||||
"claude/issue-123-20240101-1234",
|
||||
"main",
|
||||
false,
|
||||
);
|
||||
|
||||
expect(result.shouldDeleteBranch).toBe(false);
|
||||
expect(result.branchLink).toBe(
|
||||
`\n[View branch](${GITHUB_SERVER_URL}/owner/repo/tree/claude/issue-123-20240101_123456)`,
|
||||
`\n[View branch](${GITHUB_SERVER_URL}/owner/repo/tree/claude/issue-123-20240101-1234)`,
|
||||
);
|
||||
expect(consoleLogSpy).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining("has no commits"),
|
||||
@@ -123,14 +123,14 @@ describe("checkAndCommitOrDeleteBranch", () => {
|
||||
mockOctokit,
|
||||
"owner",
|
||||
"repo",
|
||||
"claude/issue-123-20240101_123456",
|
||||
"claude/issue-123-20240101-1234",
|
||||
"main",
|
||||
false,
|
||||
);
|
||||
|
||||
expect(result.shouldDeleteBranch).toBe(false);
|
||||
expect(result.branchLink).toBe(
|
||||
`\n[View branch](${GITHUB_SERVER_URL}/owner/repo/tree/claude/issue-123-20240101_123456)`,
|
||||
`\n[View branch](${GITHUB_SERVER_URL}/owner/repo/tree/claude/issue-123-20240101-1234)`,
|
||||
);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
"Error comparing commits on Claude branch:",
|
||||
@@ -146,7 +146,7 @@ describe("checkAndCommitOrDeleteBranch", () => {
|
||||
mockOctokit,
|
||||
"owner",
|
||||
"repo",
|
||||
"claude/issue-123-20240101_123456",
|
||||
"claude/issue-123-20240101-1234",
|
||||
"main",
|
||||
true, // commit signing enabled - will try to delete
|
||||
);
|
||||
@@ -154,7 +154,7 @@ describe("checkAndCommitOrDeleteBranch", () => {
|
||||
expect(result.shouldDeleteBranch).toBe(true);
|
||||
expect(result.branchLink).toBe("");
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
"Failed to delete branch claude/issue-123-20240101_123456:",
|
||||
"Failed to delete branch claude/issue-123-20240101-1234:",
|
||||
deleteError,
|
||||
);
|
||||
});
|
||||
@@ -170,7 +170,7 @@ describe("checkAndCommitOrDeleteBranch", () => {
|
||||
mockOctokit,
|
||||
"owner",
|
||||
"repo",
|
||||
"claude/issue-123-20240101_123456",
|
||||
"claude/issue-123-20240101-1234",
|
||||
"main",
|
||||
false,
|
||||
);
|
||||
@@ -178,10 +178,10 @@ describe("checkAndCommitOrDeleteBranch", () => {
|
||||
expect(result.shouldDeleteBranch).toBe(false);
|
||||
expect(result.branchLink).toBe("");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
"Branch claude/issue-123-20240101_123456 does not exist remotely",
|
||||
"Branch claude/issue-123-20240101-1234 does not exist remotely",
|
||||
);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
"Branch claude/issue-123-20240101_123456 does not exist remotely, no branch link will be added",
|
||||
"Branch claude/issue-123-20240101-1234 does not exist remotely, no branch link will be added",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,12 +103,12 @@ describe("updateCommentBody", () => {
|
||||
it("adds branch name with link to header when provided", () => {
|
||||
const input = {
|
||||
...baseInput,
|
||||
branchName: "claude/issue-123-20240101_120000",
|
||||
branchName: "claude/issue-123-20240101-1200",
|
||||
};
|
||||
|
||||
const result = updateCommentBody(input);
|
||||
expect(result).toContain(
|
||||
"• [`claude/issue-123-20240101_120000`](https://github.com/owner/repo/tree/claude/issue-123-20240101_120000)",
|
||||
"• [`claude/issue-123-20240101-1200`](https://github.com/owner/repo/tree/claude/issue-123-20240101-1200)",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -384,9 +384,9 @@ describe("updateCommentBody", () => {
|
||||
const input = {
|
||||
...baseInput,
|
||||
currentBody: "Claude Code is working… <img src='spinner.gif' />",
|
||||
branchName: "claude/pr-456-20240101_120000",
|
||||
branchName: "claude/pr-456-20240101-1200",
|
||||
prLink:
|
||||
"\n[Create a PR](https://github.com/owner/repo/compare/main...claude/pr-456-20240101_120000)",
|
||||
"\n[Create a PR](https://github.com/owner/repo/compare/main...claude/pr-456-20240101-1200)",
|
||||
triggerUsername: "jane-doe",
|
||||
};
|
||||
|
||||
@@ -394,7 +394,7 @@ describe("updateCommentBody", () => {
|
||||
|
||||
// Should include the PR link in the formatted style
|
||||
expect(result).toContain(
|
||||
"• [Create PR ➔](https://github.com/owner/repo/compare/main...claude/pr-456-20240101_120000)",
|
||||
"• [Create PR ➔](https://github.com/owner/repo/compare/main...claude/pr-456-20240101-1200)",
|
||||
);
|
||||
expect(result).toContain("**Claude finished @jane-doe's task**");
|
||||
});
|
||||
@@ -403,21 +403,21 @@ describe("updateCommentBody", () => {
|
||||
const input = {
|
||||
...baseInput,
|
||||
currentBody: "Claude Code is working…",
|
||||
branchName: "claude/issue-123-20240101_120000",
|
||||
branchName: "claude/issue-123-20240101-1200",
|
||||
branchLink:
|
||||
"\n[View branch](https://github.com/owner/repo/tree/claude/issue-123-20240101_120000)",
|
||||
"\n[View branch](https://github.com/owner/repo/tree/claude/issue-123-20240101-1200)",
|
||||
prLink:
|
||||
"\n[Create a PR](https://github.com/owner/repo/compare/main...claude/issue-123-20240101_120000)",
|
||||
"\n[Create a PR](https://github.com/owner/repo/compare/main...claude/issue-123-20240101-1200)",
|
||||
};
|
||||
|
||||
const result = updateCommentBody(input);
|
||||
|
||||
// Should include both links in formatted style
|
||||
expect(result).toContain(
|
||||
"• [`claude/issue-123-20240101_120000`](https://github.com/owner/repo/tree/claude/issue-123-20240101_120000)",
|
||||
"• [`claude/issue-123-20240101-1200`](https://github.com/owner/repo/tree/claude/issue-123-20240101-1200)",
|
||||
);
|
||||
expect(result).toContain(
|
||||
"• [Create PR ➔](https://github.com/owner/repo/compare/main...claude/issue-123-20240101_120000)",
|
||||
"• [Create PR ➔](https://github.com/owner/repo/compare/main...claude/issue-123-20240101-1200)",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ describe("generatePrompt", () => {
|
||||
commentId: "67890",
|
||||
isPR: false,
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-67890-20240101_120000",
|
||||
claudeBranch: "claude/issue-67890-20240101-1200",
|
||||
issueNumber: "67890",
|
||||
commentBody: "@claude please fix this",
|
||||
},
|
||||
@@ -183,7 +183,7 @@ describe("generatePrompt", () => {
|
||||
isPR: false,
|
||||
issueNumber: "789",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-789-20240101_120000",
|
||||
claudeBranch: "claude/issue-789-20240101-1200",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -210,7 +210,7 @@ describe("generatePrompt", () => {
|
||||
isPR: false,
|
||||
issueNumber: "999",
|
||||
baseBranch: "develop",
|
||||
claudeBranch: "claude/issue-999-20240101_120000",
|
||||
claudeBranch: "claude/issue-999-20240101-1200",
|
||||
assigneeTrigger: "claude-bot",
|
||||
},
|
||||
};
|
||||
@@ -237,7 +237,7 @@ describe("generatePrompt", () => {
|
||||
isPR: false,
|
||||
issueNumber: "888",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-888-20240101_120000",
|
||||
claudeBranch: "claude/issue-888-20240101-1200",
|
||||
labelTrigger: "claude-task",
|
||||
},
|
||||
};
|
||||
@@ -265,7 +265,7 @@ describe("generatePrompt", () => {
|
||||
isPR: false,
|
||||
issueNumber: "789",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-789-20240101_120000",
|
||||
claudeBranch: "claude/issue-789-20240101-1200",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -312,7 +312,7 @@ describe("generatePrompt", () => {
|
||||
isPR: false,
|
||||
issueNumber: "123",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-67890-20240101_120000",
|
||||
claudeBranch: "claude/issue-67890-20240101-1200",
|
||||
commentBody: "@claude please fix this",
|
||||
},
|
||||
};
|
||||
@@ -334,7 +334,7 @@ describe("generatePrompt", () => {
|
||||
isPR: false,
|
||||
issueNumber: "123",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-67890-20240101_120000",
|
||||
claudeBranch: "claude/issue-67890-20240101-1200",
|
||||
commentBody: "@claude please fix this",
|
||||
},
|
||||
};
|
||||
@@ -388,7 +388,7 @@ describe("generatePrompt", () => {
|
||||
isPR: false,
|
||||
issueNumber: "789",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-789-20240101_120000",
|
||||
claudeBranch: "claude/issue-789-20240101-1200",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -396,10 +396,10 @@ describe("generatePrompt", () => {
|
||||
|
||||
// Should contain Issue-specific instructions
|
||||
expect(prompt).toContain(
|
||||
"You are already on the correct branch (claude/issue-789-20240101_120000)",
|
||||
"You are already on the correct branch (claude/issue-789-20240101-1200)",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"IMPORTANT: You are already on the correct branch (claude/issue-789-20240101_120000)",
|
||||
"IMPORTANT: You are already on the correct branch (claude/issue-789-20240101-1200)",
|
||||
);
|
||||
expect(prompt).toContain("Create a PR](https://github.com/");
|
||||
expect(prompt).toContain(
|
||||
@@ -426,7 +426,7 @@ describe("generatePrompt", () => {
|
||||
isPR: false,
|
||||
issueNumber: "123",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-123-20240101_120000",
|
||||
claudeBranch: "claude/issue-123-20240101-1200",
|
||||
commentBody: "@claude please fix this",
|
||||
},
|
||||
};
|
||||
@@ -435,13 +435,13 @@ describe("generatePrompt", () => {
|
||||
|
||||
// Should contain the actual branch name with timestamp
|
||||
expect(prompt).toContain(
|
||||
"You are already on the correct branch (claude/issue-123-20240101_120000)",
|
||||
"You are already on the correct branch (claude/issue-123-20240101-1200)",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"IMPORTANT: You are already on the correct branch (claude/issue-123-20240101_120000)",
|
||||
"IMPORTANT: You are already on the correct branch (claude/issue-123-20240101-1200)",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"The branch-name is the current branch: claude/issue-123-20240101_120000",
|
||||
"The branch-name is the current branch: claude/issue-123-20240101-1200",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -456,7 +456,7 @@ describe("generatePrompt", () => {
|
||||
isPR: true,
|
||||
prNumber: "456",
|
||||
commentBody: "@claude please fix this",
|
||||
claudeBranch: "claude/pr-456-20240101_120000",
|
||||
claudeBranch: "claude/pr-456-20240101-1200",
|
||||
baseBranch: "main",
|
||||
},
|
||||
};
|
||||
@@ -465,13 +465,13 @@ describe("generatePrompt", () => {
|
||||
|
||||
// Should contain branch-specific instructions like issues
|
||||
expect(prompt).toContain(
|
||||
"You are already on the correct branch (claude/pr-456-20240101_120000)",
|
||||
"You are already on the correct branch (claude/pr-456-20240101-1200)",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"Create a PR](https://github.com/owner/repo/compare/main",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"The branch-name is the current branch: claude/pr-456-20240101_120000",
|
||||
"The branch-name is the current branch: claude/pr-456-20240101-1200",
|
||||
);
|
||||
expect(prompt).toContain("Reference to the original PR");
|
||||
expect(prompt).toContain(
|
||||
@@ -525,7 +525,7 @@ describe("generatePrompt", () => {
|
||||
isPR: true,
|
||||
prNumber: "789",
|
||||
commentBody: "@claude please update this",
|
||||
claudeBranch: "claude/pr-789-20240101_123000",
|
||||
claudeBranch: "claude/pr-789-20240101-1230",
|
||||
baseBranch: "develop",
|
||||
},
|
||||
};
|
||||
@@ -534,7 +534,7 @@ describe("generatePrompt", () => {
|
||||
|
||||
// Should contain new branch instructions
|
||||
expect(prompt).toContain(
|
||||
"You are already on the correct branch (claude/pr-789-20240101_123000)",
|
||||
"You are already on the correct branch (claude/pr-789-20240101-1230)",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"Create a PR](https://github.com/owner/repo/compare/develop",
|
||||
@@ -553,7 +553,7 @@ describe("generatePrompt", () => {
|
||||
prNumber: "999",
|
||||
commentId: "review-comment-123",
|
||||
commentBody: "@claude fix this issue",
|
||||
claudeBranch: "claude/pr-999-20240101_140000",
|
||||
claudeBranch: "claude/pr-999-20240101-1400",
|
||||
baseBranch: "main",
|
||||
},
|
||||
};
|
||||
@@ -562,7 +562,7 @@ describe("generatePrompt", () => {
|
||||
|
||||
// Should contain new branch instructions
|
||||
expect(prompt).toContain(
|
||||
"You are already on the correct branch (claude/pr-999-20240101_140000)",
|
||||
"You are already on the correct branch (claude/pr-999-20240101-1400)",
|
||||
);
|
||||
expect(prompt).toContain("Create a PR](https://github.com/");
|
||||
expect(prompt).toContain("Reference to the original PR");
|
||||
@@ -581,7 +581,7 @@ describe("generatePrompt", () => {
|
||||
eventAction: "closed",
|
||||
isPR: true,
|
||||
prNumber: "555",
|
||||
claudeBranch: "claude/pr-555-20240101_150000",
|
||||
claudeBranch: "claude/pr-555-20240101-1500",
|
||||
baseBranch: "main",
|
||||
},
|
||||
};
|
||||
@@ -590,7 +590,7 @@ describe("generatePrompt", () => {
|
||||
|
||||
// Should contain new branch instructions
|
||||
expect(prompt).toContain(
|
||||
"You are already on the correct branch (claude/pr-555-20240101_150000)",
|
||||
"You are already on the correct branch (claude/pr-555-20240101-1500)",
|
||||
);
|
||||
expect(prompt).toContain("Create a PR](https://github.com/");
|
||||
expect(prompt).toContain("Reference to the original PR");
|
||||
@@ -683,7 +683,7 @@ describe("getEventTypeAndContext", () => {
|
||||
isPR: false,
|
||||
issueNumber: "999",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-999-20240101_120000",
|
||||
claudeBranch: "claude/issue-999-20240101-1200",
|
||||
assigneeTrigger: "claude-bot",
|
||||
},
|
||||
};
|
||||
@@ -705,7 +705,7 @@ describe("getEventTypeAndContext", () => {
|
||||
isPR: false,
|
||||
issueNumber: "888",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-888-20240101_120000",
|
||||
claudeBranch: "claude/issue-888-20240101-1200",
|
||||
labelTrigger: "claude-task",
|
||||
},
|
||||
};
|
||||
@@ -728,7 +728,7 @@ describe("getEventTypeAndContext", () => {
|
||||
isPR: false,
|
||||
issueNumber: "999",
|
||||
baseBranch: "main",
|
||||
claudeBranch: "claude/issue-999-20240101_120000",
|
||||
claudeBranch: "claude/issue-999-20240101-1200",
|
||||
// No assigneeTrigger when using directPrompt
|
||||
},
|
||||
};
|
||||
|
||||
@@ -88,6 +88,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: mockContext,
|
||||
});
|
||||
@@ -118,6 +119,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: contextWithSigning,
|
||||
});
|
||||
@@ -143,6 +145,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [
|
||||
"mcp__github__create_issue",
|
||||
"mcp__github_file_ops__commit_files",
|
||||
@@ -174,6 +177,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [
|
||||
"mcp__github_file_ops__commit_files",
|
||||
"mcp__github_file_ops__update_claude_comment",
|
||||
@@ -193,6 +197,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: ["Edit", "Read", "Write"],
|
||||
context: mockContext,
|
||||
});
|
||||
@@ -210,6 +215,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: "",
|
||||
allowedTools: [],
|
||||
context: mockContext,
|
||||
@@ -228,6 +234,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: " \n\t ",
|
||||
allowedTools: [],
|
||||
context: mockContext,
|
||||
@@ -258,6 +265,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: additionalConfig,
|
||||
allowedTools: [
|
||||
"mcp__github__create_issue",
|
||||
@@ -296,6 +304,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: additionalConfig,
|
||||
allowedTools: [
|
||||
"mcp__github__create_issue",
|
||||
@@ -337,6 +346,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: additionalConfig,
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
@@ -357,6 +367,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: invalidJson,
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
@@ -378,6 +389,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: nonObjectJson,
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
@@ -402,6 +414,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: nullJson,
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
@@ -426,6 +439,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: arrayJson,
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
@@ -473,6 +487,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
additionalMcpConfig: additionalConfig,
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
@@ -496,6 +511,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
});
|
||||
@@ -517,6 +533,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
});
|
||||
@@ -545,6 +562,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: contextWithPermissions,
|
||||
});
|
||||
@@ -564,6 +582,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: mockContextWithSigning,
|
||||
});
|
||||
@@ -582,6 +601,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: mockPRContextWithSigning,
|
||||
});
|
||||
@@ -613,6 +633,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: contextWithPermissions,
|
||||
});
|
||||
@@ -641,6 +662,7 @@ describe("prepareMcpConfig", () => {
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
branch: "test-branch",
|
||||
baseBranch: "main",
|
||||
allowedTools: [],
|
||||
context: contextWithPermissions,
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
process.env = {
|
||||
...BASE_ENV,
|
||||
BASE_BRANCH: "main",
|
||||
CLAUDE_BRANCH: "claude/issue-67890-20240101_120000",
|
||||
CLAUDE_BRANCH: "claude/issue-67890-20240101-1200",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
mockIssueCommentContext,
|
||||
"12345",
|
||||
"main",
|
||||
"claude/issue-67890-20240101_120000",
|
||||
"claude/issue-67890-20240101-1200",
|
||||
);
|
||||
|
||||
expect(result.repository).toBe("test-owner/test-repo");
|
||||
@@ -60,7 +60,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
expect(result.eventData.issueNumber).toBe("55");
|
||||
expect(result.eventData.commentId).toBe("12345678");
|
||||
expect(result.eventData.claudeBranch).toBe(
|
||||
"claude/issue-67890-20240101_120000",
|
||||
"claude/issue-67890-20240101-1200",
|
||||
);
|
||||
expect(result.eventData.baseBranch).toBe("main");
|
||||
expect(result.eventData.commentBody).toBe(
|
||||
@@ -81,7 +81,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
mockIssueCommentContext,
|
||||
"12345",
|
||||
undefined,
|
||||
"claude/issue-67890-20240101_120000",
|
||||
"claude/issue-67890-20240101-1200",
|
||||
),
|
||||
).toThrow("BASE_BRANCH is required for issue_comment event");
|
||||
});
|
||||
@@ -152,7 +152,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
process.env = {
|
||||
...BASE_ENV,
|
||||
BASE_BRANCH: "main",
|
||||
CLAUDE_BRANCH: "claude/issue-42-20240101_120000",
|
||||
CLAUDE_BRANCH: "claude/issue-42-20240101-1200",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -161,7 +161,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
mockIssueOpenedContext,
|
||||
"12345",
|
||||
"main",
|
||||
"claude/issue-42-20240101_120000",
|
||||
"claude/issue-42-20240101-1200",
|
||||
);
|
||||
|
||||
expect(result.eventData.eventName).toBe("issues");
|
||||
@@ -174,7 +174,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
expect(result.eventData.issueNumber).toBe("42");
|
||||
expect(result.eventData.baseBranch).toBe("main");
|
||||
expect(result.eventData.claudeBranch).toBe(
|
||||
"claude/issue-42-20240101_120000",
|
||||
"claude/issue-42-20240101-1200",
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -184,7 +184,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
mockIssueAssignedContext,
|
||||
"12345",
|
||||
"main",
|
||||
"claude/issue-123-20240101_120000",
|
||||
"claude/issue-123-20240101-1200",
|
||||
);
|
||||
|
||||
expect(result.eventData.eventName).toBe("issues");
|
||||
@@ -197,7 +197,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
expect(result.eventData.issueNumber).toBe("123");
|
||||
expect(result.eventData.baseBranch).toBe("main");
|
||||
expect(result.eventData.claudeBranch).toBe(
|
||||
"claude/issue-123-20240101_120000",
|
||||
"claude/issue-123-20240101-1200",
|
||||
);
|
||||
expect(result.eventData.assigneeTrigger).toBe("@claude-bot");
|
||||
}
|
||||
@@ -215,7 +215,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
mockIssueOpenedContext,
|
||||
"12345",
|
||||
undefined,
|
||||
"claude/issue-42-20240101_120000",
|
||||
"claude/issue-42-20240101-1200",
|
||||
),
|
||||
).toThrow("BASE_BRANCH is required for issues event");
|
||||
});
|
||||
@@ -234,7 +234,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
contextWithDirectPrompt,
|
||||
"12345",
|
||||
"main",
|
||||
"claude/issue-123-20240101_120000",
|
||||
"claude/issue-123-20240101-1200",
|
||||
);
|
||||
|
||||
expect(result.eventData.eventName).toBe("issues");
|
||||
@@ -264,7 +264,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
contextWithoutTriggers,
|
||||
"12345",
|
||||
"main",
|
||||
"claude/issue-123-20240101_120000",
|
||||
"claude/issue-123-20240101-1200",
|
||||
),
|
||||
).toThrow("ASSIGNEE_TRIGGER is required for issue assigned event");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user