Compare commits

..

4 Commits

Author SHA1 Message Date
Ashwin Bhat
5c04112174 refactor: move BASE_BRANCH env reading into parseGitHubContext
- Move BASE_BRANCH environment variable reading into parseGitHubContext for consistency
- Update setupBranch to use context.inputs.baseBranch instead of process.env
- Fix test descriptions to correctly reference BASE_BRANCH instead of DEFAULT_BRANCH
- Update test environment setup to use BASE_BRANCH

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-28 21:37:58 -07:00
Ashwin Bhat
aa82b5b5ac fix: properly handle base branch throughout the action workflow
- Fix TypeScript error where defaultBranch was used before being assigned
- Replace DEFAULT_BRANCH with BASE_BRANCH in subsequent workflow steps
- Update PR creation and branch comparison to use the actual base branch
- Ensure custom base_branch input is respected in all operations

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-28 21:22:54 -07:00
claude[bot]
1fda575316 perf: optimize setupBranch to avoid unnecessary default branch fetch
Only fetch repository default branch when actually needed:
- Skip initial fetch when baseBranch is provided
- Fetch default branch at end only for return value and GitHub Actions output
- Eliminates unnecessary API call when users specify base branch

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-05-28 00:17:01 +00:00
claude[bot]
db6d3621d7 feat: add base_branch input to specify source branch for new Claude branches
- Add base_branch input parameter to action.yml allowing users to specify which branch to use as source
- Update setupBranch function to accept and use the base branch parameter  
- Defaults to repository default branch if no base branch is specified
- Addresses issue #62 for better branch control

Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
2025-05-27 19:23:38 +00:00
17 changed files with 98 additions and 386 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

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

View File

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

1
.gitignore vendored
View File

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

156
FAQ.md
View File

@@ -1,156 +0,0 @@
# Frequently Asked Questions (FAQ)
This FAQ addresses common questions and gotchas when using the Claude Code GitHub Action.
## Triggering and Authentication
### Why doesn't tagging @claude from my automated workflow work?
The `github-actions` user (and other GitHub Apps/bots) cannot trigger subsequent GitHub Actions workflows. This is a GitHub security feature to prevent infinite loops. To make this work, you need to use a Personal Access Token (PAT) instead, which will act as a regular user. When posting a comment on an issue or PR from your workflow, use your PAT instead of the `GITHUB_TOKEN` generated in your workflow.
### Why does Claude say I don't have permission to trigger it?
Only users with **write permissions** to the repository can trigger Claude. This is a security feature to prevent unauthorized use. Make sure the user commenting has at least write access to the repository.
### Why am I getting OIDC authentication errors?
If you're using the default GitHub App authentication, you must add the `id-token: write` permission to your workflow:
```yaml
permissions:
contents: read
id-token: write # Required for OIDC authentication
```
The OIDC token is required in order for the Claude GitHub app to function. If you wish to not use the GitHub app, you can instead provide a `github_token` input to the action for Claude to operate with. See the [Claude Code permissions documentation][perms] for more.
## Claude's Capabilities and Limitations
### Why won't Claude update workflow files when I ask it to?
The GitHub App for Claude doesn't have workflow write access for security reasons. This prevents Claude from modifying CI/CD configurations that could potentially create unintended consequences. This is something we may reconsider in the future.
### Why won't Claude rebase my branch?
By default, Claude only uses commit tools for non-destructive changes to the branch. Claude is configured to:
- Never push to branches other than where it was invoked (either its own branch or the PR branch)
- Never force push or perform destructive operations
You can grant additional tools via the `allowed_tools` input if needed:
```yaml
allowed_tools: "Bash(git rebase:*)" # Use with caution
```
### Why won't Claude create a pull request?
Claude doesn't create PRs by default. Instead, it pushes commits to a branch and provides a link to a pre-filled PR submission page. This approach ensures your repository's branch protection rules are still adhered to and gives you final control over PR creation.
### Why can't Claude run my tests or see CI results?
Claude cannot access GitHub Actions logs, test results, or other CI/CD outputs by default. It only has access to the repository files. If you need Claude to see test results, you can either:
1. Instruct Claude to run tests before making commits
2. Copy and paste CI results into a comment for Claude to analyze
This limitation exists for security reasons but may be reconsidered in the future based on user feedback.
### Why does Claude only update one comment instead of creating new ones?
Claude is configured to update a single comment to avoid cluttering PR/issue discussions. All of Claude's responses, including progress updates and final results, will appear in the same comment with checkboxes showing task progress.
## Branch and Commit Behavior
### Why did Claude create a new branch when commenting on a closed PR?
Claude's branch behavior depends on the context:
- **Open PRs**: Pushes directly to the existing PR branch
- **Closed/Merged PRs**: Creates a new branch (cannot push to closed PR branches)
- **Issues**: Always creates a new branch with a timestamp
### Why are my commits shallow/missing history?
For performance, Claude uses shallow clones:
- PRs: `--depth=20` (last 20 commits)
- New branches: `--depth=1` (single commit)
If you need full history, you can configure this in your workflow before calling Claude in the `actions/checkout` step.
```
- uses: actions/checkout@v4
depth: 0 # will fetch full repo history
```
## Configuration and Tools
### What's the difference between `direct_prompt` and `custom_instructions`?
These inputs serve different purposes in how Claude responds:
- **`direct_prompt`**: Bypasses trigger detection entirely. When provided, Claude executes this exact instruction regardless of comments or mentions. Perfect for automated workflows where you want Claude to perform a specific task on every run (e.g., "Update the API documentation based on changes in this PR").
- **`custom_instructions`**: Additional context added to Claude's system prompt while still respecting normal triggers. These instructions modify Claude's behavior but don't replace the triggering comment. Use this to give Claude standing instructions like "You have been granted additional tools for ...".
Example:
```yaml
# Using direct_prompt - runs automatically without @claude mention
direct_prompt: "Review this PR for security vulnerabilities"
# Using custom_instructions - still requires @claude trigger
custom_instructions: "Focus on performance implications and suggest optimizations"
```
### Why doesn't Claude execute my bash commands?
The Bash tool is **disabled by default** for security. To enable individual bash commands:
```yaml
allowed_tools: "Bash(npm:*),Bash(git:*)" # Allows only npm and git commands
```
### Can Claude work across multiple repositories?
No, Claude's GitHub app token is sandboxed to the current repository only. It cannot push to any other repositories. It can, however, read public repositories, but to get access to this, you must configure it with tools to do so.
## MCP Servers and Extended Functionality
### What MCP servers are available by default?
Claude Code Action automatically configures two MCP servers:
1. **GitHub MCP server**: For GitHub API operations
2. **File operations server**: For advanced file manipulation
However, tools from these servers still need to be explicitly allowed via `allowed_tools`.
## Troubleshooting
### How can I debug what Claude is doing?
Check the GitHub Action log for Claude's run for the full execution trace.
### Why can't I trigger Claude with `@claude-mention` or `claude!`?
The trigger uses word boundaries, so `@claude` must be a complete word. Variations like `@claude-bot`, `@claude!`, or `claude@mention` won't work unless you customize the `trigger_phrase`.
## Best Practices
1. **Always specify permissions explicitly** in your workflow file
2. **Use GitHub Secrets** for API keys - never hardcode them
3. **Be specific with `allowed_tools`** - only enable what's necessary
4. **Test in a separate branch** before using on important PRs
5. **Monitor Claude's token usage** to avoid hitting API limits
6. **Review Claude's changes** carefully before merging
## Getting Help
If you encounter issues not covered here:
1. Check the [GitHub Issues](https://github.com/anthropics/claude-code-action/issues)
2. Review the [example workflows](https://github.com/anthropics/claude-code-action#examples)
[perms]: https://docs.anthropic.com/en/docs/claude-code/settings#permissions

View File

@@ -33,10 +33,6 @@ This command will guide you through setting up the GitHub app and required secre
2. Add `ANTHROPIC_API_KEY` to your repository secrets ([Learn how to use secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions))
3. Copy the workflow file from [`examples/claude.yml`](./examples/claude.yml) into your repository's `.github/workflows/`
## 📚 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.
## Usage
Add a workflow file to your repository (e.g., `.github/workflows/claude.yml`):
@@ -450,7 +446,7 @@ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```
This applies to all sensitive values including API keys, access tokens, and credentials.
We also recommend that you always use short-lived tokens when possible
We also reccomend that you always use short-lived tokens when possible
## License

View File

@@ -12,6 +12,9 @@ inputs:
assignee_trigger:
description: "The assignee username that triggers the action (e.g. @claude)"
required: false
base_branch:
description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)"
required: false
# Claude Code configuration
model:
@@ -85,6 +88,7 @@ runs:
env:
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
BASE_BRANCH: ${{ inputs.base_branch }}
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }}
DIRECT_PROMPT: ${{ inputs.direct_prompt }}
@@ -94,7 +98,7 @@ runs:
- name: Run Claude Code
id: claude-code
if: steps.prepare.outputs.contains_trigger == 'true'
uses: anthropics/claude-code-base-action@78eef48a8f466f7a800a2315134506d4c7ad9163 # v0.0.7
uses: anthropics/claude-code-base-action@5097b6cdfe5fc5a3ac0166cc344c34ed23c93982 # https://github.com/anthropics/claude-code-base-action/releases/tag/v0.0.5
with:
prompt_file: /tmp/claude-prompts/claude-prompt.txt
allowed_tools: ${{ env.ALLOWED_TOOLS }}
@@ -143,7 +147,7 @@ runs:
TRIGGER_COMMENT_ID: ${{ github.event.comment.id }}
CLAUDE_BRANCH: ${{ steps.prepare.outputs.CLAUDE_BRANCH }}
IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }}
DEFAULT_BRANCH: ${{ steps.prepare.outputs.DEFAULT_BRANCH }}
BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }}
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }}
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -69,7 +69,7 @@ export function buildDisallowedToolsString(
export function prepareContext(
context: ParsedGitHubContext,
claudeCommentId: string,
defaultBranch?: string,
baseBranch?: string,
claudeBranch?: string,
): PreparedContext {
const repository = context.repository.full_name;
@@ -147,7 +147,7 @@ export function prepareContext(
...(commentId && { commentId }),
commentBody,
...(claudeBranch && { claudeBranch }),
...(defaultBranch && { defaultBranch }),
...(baseBranch && { baseBranch }),
};
break;
@@ -169,7 +169,7 @@ export function prepareContext(
prNumber,
commentBody,
...(claudeBranch && { claudeBranch }),
...(defaultBranch && { defaultBranch }),
...(baseBranch && { baseBranch }),
};
break;
@@ -194,13 +194,13 @@ export function prepareContext(
prNumber,
commentBody,
...(claudeBranch && { claudeBranch }),
...(defaultBranch && { defaultBranch }),
...(baseBranch && { baseBranch }),
};
break;
} else if (!claudeBranch) {
throw new Error("CLAUDE_BRANCH is required for issue_comment event");
} else if (!defaultBranch) {
throw new Error("DEFAULT_BRANCH is required for issue_comment event");
} else if (!baseBranch) {
throw new Error("BASE_BRANCH is required for issue_comment event");
} else if (!issueNumber) {
throw new Error(
"ISSUE_NUMBER is required for issue_comment event for issues",
@@ -212,7 +212,7 @@ export function prepareContext(
commentId,
isPR: false,
claudeBranch: claudeBranch,
defaultBranch,
baseBranch,
issueNumber,
commentBody,
};
@@ -228,8 +228,8 @@ export function prepareContext(
if (isPR) {
throw new Error("IS_PR must be false for issues event");
}
if (!defaultBranch) {
throw new Error("DEFAULT_BRANCH is required for issues event");
if (!baseBranch) {
throw new Error("BASE_BRANCH is required for issues event");
}
if (!claudeBranch) {
throw new Error("CLAUDE_BRANCH is required for issues event");
@@ -246,7 +246,7 @@ export function prepareContext(
eventAction: "assigned",
isPR: false,
issueNumber,
defaultBranch,
baseBranch,
claudeBranch,
assigneeTrigger,
};
@@ -256,7 +256,7 @@ export function prepareContext(
eventAction: "opened",
isPR: false,
issueNumber,
defaultBranch,
baseBranch,
claudeBranch,
};
} else {
@@ -277,7 +277,7 @@ export function prepareContext(
isPR: true,
prNumber,
...(claudeBranch && { claudeBranch }),
...(defaultBranch && { defaultBranch }),
...(baseBranch && { baseBranch }),
};
break;
@@ -524,13 +524,13 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov
${
eventData.claudeBranch
? `- Provide a URL to create a PR manually in this format:
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.defaultBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.baseBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
- IMPORTANT: Use THREE dots (...) between branch names, not two (..)
Example: ${GITHUB_SERVER_URL}/${context.repository}/compare/main...feature-branch (correct)
NOT: ${GITHUB_SERVER_URL}/${context.repository}/compare/main..feature-branch (incorrect)
- IMPORTANT: Ensure all URL parameters are properly encoded - spaces should be encoded as %20, not left as spaces
Example: Instead of "fix: update welcome message", use "fix%3A%20update%20welcome%20message"
- The target-branch should be '${eventData.defaultBranch}'.
- The target-branch should be '${eventData.baseBranch}'.
- The branch-name is the current branch: ${eventData.claudeBranch}
- The body should include:
- A clear description of the changes
@@ -594,11 +594,6 @@ What You CANNOT Do:
- Execute commands outside the repository context
- Run arbitrary Bash commands (unless explicitly allowed via allowed_tools configuration)
- Perform branch operations (cannot merge branches, rebase, or perform other git operations beyond pushing commits)
- Modify files in the .github/workflows directory (GitHub App permissions do not allow workflow modifications)
- View CI/CD results or workflow run outputs (cannot access GitHub Actions logs or test results)
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)."
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.
@@ -620,7 +615,7 @@ f. If you are unable to complete certain steps, such as running a linter or test
export async function createPrompt(
claudeCommentId: number,
defaultBranch: string | undefined,
baseBranch: string | undefined,
claudeBranch: string | undefined,
githubData: FetchDataResult,
context: ParsedGitHubContext,
@@ -629,7 +624,7 @@ export async function createPrompt(
const preparedContext = prepareContext(
context,
claudeCommentId.toString(),
defaultBranch,
baseBranch,
claudeBranch,
);

View File

@@ -16,7 +16,7 @@ type PullRequestReviewCommentEvent = {
commentId?: string; // May be present for review comments
commentBody: string;
claudeBranch?: string;
defaultBranch?: string;
baseBranch?: string;
};
type PullRequestReviewEvent = {
@@ -25,7 +25,7 @@ type PullRequestReviewEvent = {
prNumber: string;
commentBody: string;
claudeBranch?: string;
defaultBranch?: string;
baseBranch?: string;
};
type IssueCommentEvent = {
@@ -33,7 +33,7 @@ type IssueCommentEvent = {
commentId: string;
issueNumber: string;
isPR: false;
defaultBranch: string;
baseBranch: string;
claudeBranch: string;
commentBody: string;
};
@@ -46,7 +46,7 @@ type PullRequestCommentEvent = {
isPR: true;
commentBody: string;
claudeBranch?: string;
defaultBranch?: string;
baseBranch?: string;
};
type IssueOpenedEvent = {
@@ -54,7 +54,7 @@ type IssueOpenedEvent = {
eventAction: "opened";
isPR: false;
issueNumber: string;
defaultBranch: string;
baseBranch: string;
claudeBranch: string;
};
@@ -63,7 +63,7 @@ type IssueAssignedEvent = {
eventAction: "assigned";
isPR: false;
issueNumber: string;
defaultBranch: string;
baseBranch: string;
claudeBranch: string;
assigneeTrigger: string;
};
@@ -74,7 +74,7 @@ type PullRequestEvent = {
isPR: true;
prNumber: string;
claudeBranch?: string;
defaultBranch?: string;
baseBranch?: string;
};
// Union type for all possible event types

View File

@@ -77,7 +77,7 @@ async function run() {
// Step 10: Create prompt file
await createPrompt(
commentId,
branchInfo.defaultBranch,
branchInfo.baseBranch,
branchInfo.claudeBranch,
githubData,
context,

View File

@@ -18,7 +18,7 @@ async function run() {
const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!);
const githubToken = process.env.GITHUB_TOKEN!;
const claudeBranch = process.env.CLAUDE_BRANCH;
const defaultBranch = process.env.DEFAULT_BRANCH || "main";
const baseBranch = process.env.BASE_BRANCH || "main";
const triggerUsername = process.env.TRIGGER_USERNAME;
const context = parseGitHubContext();
@@ -92,7 +92,7 @@ async function run() {
owner,
repo,
claudeBranch,
defaultBranch,
baseBranch,
);
// Check if we need to add PR URL when we have a new branch
@@ -102,7 +102,7 @@ async function run() {
// Check if comment already contains a PR URL
const serverUrlPattern = serverUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const prUrlPattern = new RegExp(
`${serverUrlPattern}\\/.+\\/compare\\/${defaultBranch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\.\\.`,
`${serverUrlPattern}\\/.+\\/compare\\/${baseBranch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\.\\.`,
);
const containsPRUrl = currentBody.match(prUrlPattern);
@@ -113,7 +113,7 @@ async function run() {
await octokit.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: `${defaultBranch}...${claudeBranch}`,
basehead: `${baseBranch}...${claudeBranch}`,
});
// If there are changes (commits or file changes), add the PR URL
@@ -128,7 +128,7 @@ async function run() {
const prBody = encodeURIComponent(
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
);
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${defaultBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
prLink = `\n[Create a PR](${prUrl})`;
}
} catch (error) {

View File

@@ -32,6 +32,7 @@ export type ParsedGitHubContext = {
disallowedTools: string;
customInstructions: string;
directPrompt: string;
baseBranch?: string;
};
};
@@ -55,6 +56,7 @@ export function parseGitHubContext(): ParsedGitHubContext {
disallowedTools: process.env.DISALLOWED_TOOLS ?? "",
customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "",
directPrompt: process.env.DIRECT_PROMPT ?? "",
baseBranch: process.env.BASE_BRANCH,
},
};

View File

@@ -6,7 +6,7 @@ export async function checkAndDeleteEmptyBranch(
owner: string,
repo: string,
claudeBranch: string | undefined,
defaultBranch: string,
baseBranch: string,
): Promise<{ shouldDeleteBranch: boolean; branchLink: string }> {
let branchLink = "";
let shouldDeleteBranch = false;
@@ -18,7 +18,7 @@ export async function checkAndDeleteEmptyBranch(
await octokit.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: `${defaultBranch}...${claudeBranch}`,
basehead: `${baseBranch}...${claudeBranch}`,
});
// If there are no commits, mark branch for deletion

View File

@@ -14,7 +14,7 @@ import type { Octokits } from "../api/client";
import type { FetchDataResult } from "../data/fetcher";
export type BranchInfo = {
defaultBranch: string;
baseBranch: string;
claudeBranch?: string;
currentBranch: string;
};
@@ -26,15 +26,9 @@ export async function setupBranch(
): Promise<BranchInfo> {
const { owner, repo } = context.repository;
const entityNumber = context.entityNumber;
const { baseBranch } = context.inputs;
const isPR = context.isPR;
// Get the default branch first
const repoResponse = await octokits.rest.repos.get({
owner,
repo,
});
const defaultBranch = repoResponse.data.default_branch;
if (isPR) {
const prData = githubData.contextData as GitHubPullRequest;
const prState = prData.state;
@@ -42,7 +36,7 @@ export async function setupBranch(
// Check if PR is closed or merged
if (prState === "CLOSED" || prState === "MERGED") {
console.log(
`PR #${entityNumber} is ${prState}, creating new branch from default...`,
`PR #${entityNumber} is ${prState}, creating new branch from source...`,
);
// Fall through to create a new branch like we do for issues
} else {
@@ -51,24 +45,42 @@ export async function setupBranch(
const branchName = prData.headRefName;
// Execute git commands to checkout PR branch (shallow fetch for performance)
// Fetch the branch with a depth of 20 to avoid fetching too much history, while still allowing for some context
await $`git fetch origin --depth=20 ${branchName}`;
// Execute git commands to checkout PR branch
await $`git fetch origin ${branchName}`;
await $`git checkout ${branchName}`;
console.log(`Successfully checked out PR branch for PR #${entityNumber}`);
// For open PRs, return branch info
// For open PRs, we need to get the base branch of the PR
const baseBranch = prData.baseRefName;
return {
defaultBranch,
baseBranch,
currentBranch: branchName,
};
}
}
// Determine source branch - use baseBranch if provided, otherwise fetch default
let sourceBranch: string;
if (baseBranch) {
// Use provided base branch for source
sourceBranch = baseBranch;
} else {
// No base branch provided, fetch the default branch to use as source
const repoResponse = await octokits.rest.repos.get({
owner,
repo,
});
sourceBranch = repoResponse.data.default_branch;
}
// Creating a new branch for either an issue or closed/merged PR
const entityType = isPR ? "pr" : "issue";
console.log(`Creating new branch for ${entityType} #${entityNumber}...`);
console.log(
`Creating new branch for ${entityType} #${entityNumber} from source branch: ${sourceBranch}...`,
);
const timestamp = new Date()
.toISOString()
@@ -80,14 +92,14 @@ export async function setupBranch(
const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
try {
// Get the SHA of the default branch
const defaultBranchRef = await octokits.rest.git.getRef({
// Get the SHA of the source branch
const sourceBranchRef = await octokits.rest.git.getRef({
owner,
repo,
ref: `heads/${defaultBranch}`,
ref: `heads/${sourceBranch}`,
});
const currentSHA = defaultBranchRef.data.object.sha;
const currentSHA = sourceBranchRef.data.object.sha;
console.log(`Current SHA: ${currentSHA}`);
@@ -99,8 +111,8 @@ export async function setupBranch(
sha: currentSHA,
});
// Checkout the new branch (shallow fetch for performance)
await $`git fetch origin --depth=1 ${newBranch}`;
// Checkout the new branch
await $`git fetch origin ${newBranch}`;
await $`git checkout ${newBranch}`;
console.log(
@@ -109,9 +121,9 @@ export async function setupBranch(
// Set outputs for GitHub Actions
core.setOutput("CLAUDE_BRANCH", newBranch);
core.setOutput("DEFAULT_BRANCH", defaultBranch);
core.setOutput("BASE_BRANCH", sourceBranch);
return {
defaultBranch,
baseBranch: sourceBranch,
claudeBranch: newBranch,
currentBranch: newBranch,
};

View File

@@ -127,7 +127,7 @@ describe("generatePrompt", () => {
eventName: "issue_comment",
commentId: "67890",
isPR: false,
defaultBranch: "main",
baseBranch: "main",
claudeBranch: "claude/issue-67890-20240101_120000",
issueNumber: "67890",
commentBody: "@claude please fix this",
@@ -183,7 +183,7 @@ describe("generatePrompt", () => {
eventAction: "opened",
isPR: false,
issueNumber: "789",
defaultBranch: "main",
baseBranch: "main",
claudeBranch: "claude/issue-789-20240101_120000",
},
};
@@ -210,7 +210,7 @@ describe("generatePrompt", () => {
eventAction: "assigned",
isPR: false,
issueNumber: "999",
defaultBranch: "develop",
baseBranch: "develop",
claudeBranch: "claude/issue-999-20240101_120000",
assigneeTrigger: "claude-bot",
},
@@ -238,7 +238,7 @@ describe("generatePrompt", () => {
eventAction: "opened",
isPR: false,
issueNumber: "789",
defaultBranch: "main",
baseBranch: "main",
claudeBranch: "claude/issue-789-20240101_120000",
},
};
@@ -285,7 +285,7 @@ describe("generatePrompt", () => {
commentId: "67890",
isPR: false,
issueNumber: "123",
defaultBranch: "main",
baseBranch: "main",
claudeBranch: "claude/issue-67890-20240101_120000",
commentBody: "@claude please fix this",
},
@@ -307,7 +307,7 @@ describe("generatePrompt", () => {
commentId: "67890",
isPR: false,
issueNumber: "123",
defaultBranch: "main",
baseBranch: "main",
claudeBranch: "claude/issue-67890-20240101_120000",
commentBody: "@claude please fix this",
},
@@ -362,7 +362,7 @@ describe("generatePrompt", () => {
eventAction: "opened",
isPR: false,
issueNumber: "789",
defaultBranch: "main",
baseBranch: "main",
claudeBranch: "claude/issue-789-20240101_120000",
},
};
@@ -400,7 +400,7 @@ describe("generatePrompt", () => {
commentId: "67890",
isPR: false,
issueNumber: "123",
defaultBranch: "main",
baseBranch: "main",
claudeBranch: "claude/issue-123-20240101_120000",
commentBody: "@claude please fix this",
},
@@ -432,7 +432,7 @@ describe("generatePrompt", () => {
prNumber: "456",
commentBody: "@claude please fix this",
claudeBranch: "claude/pr-456-20240101_120000",
defaultBranch: "main",
baseBranch: "main",
},
};
@@ -470,7 +470,7 @@ describe("generatePrompt", () => {
isPR: true,
prNumber: "456",
commentBody: "@claude please fix this",
// No claudeBranch or defaultBranch for open PRs
// No claudeBranch or baseBranch for open PRs
},
};
@@ -503,7 +503,7 @@ describe("generatePrompt", () => {
prNumber: "789",
commentBody: "@claude please update this",
claudeBranch: "claude/pr-789-20240101_123000",
defaultBranch: "develop",
baseBranch: "develop",
},
};
@@ -531,7 +531,7 @@ describe("generatePrompt", () => {
commentId: "review-comment-123",
commentBody: "@claude fix this issue",
claudeBranch: "claude/pr-999-20240101_140000",
defaultBranch: "main",
baseBranch: "main",
},
};
@@ -559,7 +559,7 @@ describe("generatePrompt", () => {
isPR: true,
prNumber: "555",
claudeBranch: "claude/pr-555-20240101_150000",
defaultBranch: "main",
baseBranch: "main",
},
};
@@ -604,7 +604,7 @@ describe("getEventTypeAndContext", () => {
eventAction: "assigned",
isPR: false,
issueNumber: "999",
defaultBranch: "main",
baseBranch: "main",
claudeBranch: "claude/issue-999-20240101_120000",
assigneeTrigger: "claude-bot",
},

View File

@@ -34,7 +34,7 @@ describe("parseEnvVarsWithContext", () => {
beforeEach(() => {
process.env = {
...BASE_ENV,
DEFAULT_BRANCH: "main",
BASE_BRANCH: "main",
CLAUDE_BRANCH: "claude/issue-67890-20240101_120000",
};
});
@@ -62,7 +62,7 @@ describe("parseEnvVarsWithContext", () => {
expect(result.eventData.claudeBranch).toBe(
"claude/issue-67890-20240101_120000",
);
expect(result.eventData.defaultBranch).toBe("main");
expect(result.eventData.baseBranch).toBe("main");
expect(result.eventData.commentBody).toBe(
"@claude can you help explain how to configure the logging system?",
);
@@ -75,7 +75,7 @@ describe("parseEnvVarsWithContext", () => {
).toThrow("CLAUDE_BRANCH is required for issue_comment event");
});
test("should throw error when DEFAULT_BRANCH is missing", () => {
test("should throw error when BASE_BRANCH is missing", () => {
expect(() =>
prepareContext(
mockIssueCommentContext,
@@ -83,7 +83,7 @@ describe("parseEnvVarsWithContext", () => {
undefined,
"claude/issue-67890-20240101_120000",
),
).toThrow("DEFAULT_BRANCH is required for issue_comment event");
).toThrow("BASE_BRANCH is required for issue_comment event");
});
});
@@ -151,7 +151,7 @@ describe("parseEnvVarsWithContext", () => {
beforeEach(() => {
process.env = {
...BASE_ENV,
DEFAULT_BRANCH: "main",
BASE_BRANCH: "main",
CLAUDE_BRANCH: "claude/issue-42-20240101_120000",
};
});
@@ -172,7 +172,7 @@ describe("parseEnvVarsWithContext", () => {
result.eventData.eventAction === "opened"
) {
expect(result.eventData.issueNumber).toBe("42");
expect(result.eventData.defaultBranch).toBe("main");
expect(result.eventData.baseBranch).toBe("main");
expect(result.eventData.claudeBranch).toBe(
"claude/issue-42-20240101_120000",
);
@@ -195,7 +195,7 @@ describe("parseEnvVarsWithContext", () => {
result.eventData.eventAction === "assigned"
) {
expect(result.eventData.issueNumber).toBe("123");
expect(result.eventData.defaultBranch).toBe("main");
expect(result.eventData.baseBranch).toBe("main");
expect(result.eventData.claudeBranch).toBe(
"claude/issue-123-20240101_120000",
);
@@ -209,7 +209,7 @@ describe("parseEnvVarsWithContext", () => {
).toThrow("CLAUDE_BRANCH is required for issues event");
});
test("should throw error when DEFAULT_BRANCH is missing for issues", () => {
test("should throw error when BASE_BRANCH is missing for issues", () => {
expect(() =>
prepareContext(
mockIssueOpenedContext,
@@ -217,7 +217,7 @@ describe("parseEnvVarsWithContext", () => {
undefined,
"claude/issue-42-20240101_120000",
),
).toThrow("DEFAULT_BRANCH is required for issues event");
).toThrow("BASE_BRANCH is required for issues event");
});
});