mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
Compare commits
5 Commits
ashwin/bum
...
fix-500-er
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc172c74aa | ||
|
|
cb9aae1881 | ||
|
|
b02a95b3f3 | ||
|
|
cde954dda0 | ||
|
|
c845eee45f |
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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.
|
||||
4
.github/workflows/claude.yml
vendored
4
.github/workflows/claude.yml
vendored
@@ -31,8 +31,6 @@ jobs:
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@beta
|
||||
uses: ./
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
allowed_tools: "Bash(bun install),Bash(bun test:*),Bash(bun run format),Bash(bun typecheck)"
|
||||
custom_instructions: "You have also been granted tools for editing files and running bun commands (install, run, test, typecheck) for testing your changes: bun install, bun test, bun run format, bun typecheck."
|
||||
|
||||
104
.github/workflows/issue-triage.yml
vendored
104
.github/workflows/issue-triage.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
|
||||
**/.claude/settings.local.json
|
||||
|
||||
@@ -446,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
|
||||
|
||||
|
||||
15
action.yml
15
action.yml
@@ -12,9 +12,6 @@ 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:
|
||||
@@ -70,7 +67,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.11
|
||||
|
||||
@@ -88,7 +85,6 @@ 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 }}
|
||||
@@ -98,7 +94,7 @@ runs:
|
||||
- name: Run Claude Code
|
||||
id: claude-code
|
||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||
uses: anthropics/claude-code-base-action@c8e31bd52d9a149b3f8309d7978c6edaa282688d # v0.0.8
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
with:
|
||||
prompt_file: /tmp/claude-prompts/claude-prompt.txt
|
||||
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
||||
@@ -114,9 +110,6 @@ runs:
|
||||
ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }}
|
||||
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
||||
|
||||
# Provider configuration
|
||||
ANTHROPIC_BASE_URL: ${{ env.ANTHROPIC_BASE_URL }}
|
||||
|
||||
# AWS configuration
|
||||
AWS_REGION: ${{ env.AWS_REGION }}
|
||||
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
|
||||
@@ -150,12 +143,10 @@ 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' }}
|
||||
BASE_BRANCH: ${{ steps.prepare.outputs.BASE_BRANCH }}
|
||||
DEFAULT_BRANCH: ${{ steps.prepare.outputs.DEFAULT_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 || '' }}
|
||||
PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
|
||||
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
|
||||
|
||||
- name: Display Claude Code Report
|
||||
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
||||
|
||||
93
check-pr-status.ts
Normal file
93
check-pr-status.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bun
|
||||
// Check the status of PR #105775
|
||||
|
||||
const GITHUB_API_URL = "https://api.github.com";
|
||||
|
||||
async function checkPRStatus(token: string) {
|
||||
const headers = {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
};
|
||||
|
||||
try {
|
||||
// Check PR details
|
||||
console.log("Checking PR #105775...\n");
|
||||
const prResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/anthropics/anthropic/pulls/105775`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
console.log(`PR Status: ${prResponse.status}`);
|
||||
|
||||
if (!prResponse.ok) {
|
||||
if (prResponse.status === 404) {
|
||||
console.log("PR not found - it might be in a private repo or deleted");
|
||||
}
|
||||
const error = await prResponse.text();
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const prData = await prResponse.json();
|
||||
console.log(`Title: ${prData.title}`);
|
||||
console.log(`State: ${prData.state}`);
|
||||
console.log(`Branch: ${prData.head.ref}`);
|
||||
console.log(`Base: ${prData.base.ref}`);
|
||||
console.log(`Created: ${prData.created_at}`);
|
||||
console.log(`Updated: ${prData.updated_at}`);
|
||||
|
||||
// Check if branch still exists
|
||||
console.log(`\nChecking if branch '${prData.head.ref}' still exists...`);
|
||||
const branchResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/anthropics/anthropic/git/refs/heads/${prData.head.ref}`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (branchResponse.ok) {
|
||||
const branchData = await branchResponse.json();
|
||||
console.log(`✓ Branch exists with SHA: ${branchData.object.sha}`);
|
||||
console.log(` PR head SHA: ${prData.head.sha}`);
|
||||
if (branchData.object.sha !== prData.head.sha) {
|
||||
console.log(` ⚠️ Branch has been updated since PR was created`);
|
||||
}
|
||||
} else {
|
||||
console.log(`✗ Branch does not exist (${branchResponse.status})`);
|
||||
}
|
||||
|
||||
// Get recent comments
|
||||
console.log(`\nFetching recent comments...`);
|
||||
const commentsResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/anthropics/anthropic/issues/105775/comments?per_page=5&sort=created&direction=desc`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (commentsResponse.ok) {
|
||||
const comments = await commentsResponse.json();
|
||||
console.log(`Found ${comments.length} recent comments:`);
|
||||
|
||||
comments.reverse().forEach((comment: any, index: number) => {
|
||||
console.log(`\nComment ${index + 1}:`);
|
||||
console.log(` Author: ${comment.user.login}`);
|
||||
console.log(` Created: ${comment.created_at}`);
|
||||
console.log(` Body preview: ${comment.body.substring(0, 100)}...`);
|
||||
|
||||
// Check if it's a claude-code-action comment
|
||||
if (comment.body.includes("claude") || comment.user.login.includes("bot")) {
|
||||
console.log(` → Appears to be a Claude-related comment`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const token = process.argv[2];
|
||||
if (!token) {
|
||||
console.log("Usage: bun check-pr-status.ts <github-pat>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
checkPRStatus(token);
|
||||
183
diagnose-app-permissions.ts
Normal file
183
diagnose-app-permissions.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env bun
|
||||
// Diagnose why GitHub App permissions are inconsistent
|
||||
|
||||
const GITHUB_API_URL = "https://api.github.com";
|
||||
|
||||
async function diagnosePermissions(token: string, owner: string, repo: string) {
|
||||
const headers = {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
};
|
||||
|
||||
console.log(`\n=== Diagnosing GitHub App Permission Issues ===`);
|
||||
console.log(`Repository: ${owner}/${repo}\n`);
|
||||
|
||||
try {
|
||||
// 1. Check what type of token we have
|
||||
console.log("1. Token Analysis:");
|
||||
const authHeader = headers.Authorization;
|
||||
if (authHeader.includes('ghs_')) {
|
||||
console.log("✓ GitHub App installation token detected");
|
||||
} else if (authHeader.includes('ghp_')) {
|
||||
console.log("✓ Personal Access Token detected");
|
||||
} else {
|
||||
console.log("? Unknown token type");
|
||||
}
|
||||
|
||||
// 2. Check rate limit headers (different for apps vs users)
|
||||
console.log("\n2. Rate Limit Analysis:");
|
||||
const rateLimitResponse = await fetch(`${GITHUB_API_URL}/rate_limit`, { headers });
|
||||
if (rateLimitResponse.ok) {
|
||||
const rateData = await rateLimitResponse.json();
|
||||
console.log(` Core limit: ${rateData.rate.remaining}/${rateData.rate.limit}`);
|
||||
if (rateData.rate.limit > 5000) {
|
||||
console.log(" → Higher limit suggests GitHub App token");
|
||||
} else {
|
||||
console.log(" → Standard limit suggests user token");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Test different API endpoints to find permission boundaries
|
||||
console.log("\n3. Testing API Endpoints:");
|
||||
|
||||
// Test regular content API
|
||||
console.log("\n a) Content API (high-level):");
|
||||
const contentResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/contents/README.md`,
|
||||
{ headers }
|
||||
);
|
||||
console.log(` GET contents: ${contentResponse.status} ${contentResponse.ok ? '✓' : '✗'}`);
|
||||
|
||||
// Test git database read
|
||||
console.log("\n b) Git Database API (read):");
|
||||
const branchResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/branches`,
|
||||
{ headers }
|
||||
);
|
||||
if (branchResponse.ok) {
|
||||
const branches = await branchResponse.json();
|
||||
const defaultBranch = branches.find((b: any) => b.name === 'main' || b.name === 'master' || b.name === 'staging');
|
||||
if (defaultBranch) {
|
||||
const commitSha = defaultBranch.commit.sha;
|
||||
|
||||
// Try to read commit
|
||||
const commitResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/commits/${commitSha}`,
|
||||
{ headers }
|
||||
);
|
||||
console.log(` GET commit: ${commitResponse.status} ${commitResponse.ok ? '✓' : '✗'}`);
|
||||
|
||||
// Try to read tree
|
||||
if (commitResponse.ok) {
|
||||
const commitData = await commitResponse.json();
|
||||
const treeResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/trees/${commitData.tree.sha}`,
|
||||
{ headers }
|
||||
);
|
||||
console.log(` GET tree: ${treeResponse.status} ${treeResponse.ok ? '✓' : '✗'}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test git database write
|
||||
console.log("\n c) Git Database API (write):");
|
||||
|
||||
// Get a base commit to test with
|
||||
const testBranch = branches[0];
|
||||
if (testBranch) {
|
||||
const baseCommitResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/commits/${testBranch.commit.sha}`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (baseCommitResponse.ok) {
|
||||
const baseCommit = await baseCommitResponse.json();
|
||||
|
||||
// Try to create a blob
|
||||
const blobResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/blobs`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: "test",
|
||||
encoding: "utf-8",
|
||||
}),
|
||||
}
|
||||
);
|
||||
console.log(` POST blob: ${blobResponse.status} ${blobResponse.ok ? '✓' : '✗'}`);
|
||||
|
||||
// Try to create a tree
|
||||
const treeResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/trees`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
base_tree: baseCommit.tree.sha,
|
||||
tree: [{
|
||||
path: "test-permission-check.txt",
|
||||
mode: "100644",
|
||||
type: "blob",
|
||||
content: "test",
|
||||
}],
|
||||
}),
|
||||
}
|
||||
);
|
||||
console.log(` POST tree: ${treeResponse.status} ${treeResponse.ok ? '✓' : '✗'}`);
|
||||
|
||||
if (!treeResponse.ok) {
|
||||
const error = await treeResponse.text();
|
||||
console.log(` Error: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check webhook/app events
|
||||
console.log("\n4. Checking Recent Activity:");
|
||||
const eventsResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/events?per_page=10`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (eventsResponse.ok) {
|
||||
const events = await eventsResponse.json();
|
||||
const appEvents = events.filter((e: any) =>
|
||||
e.actor.login.includes('[bot]') ||
|
||||
e.actor.type === 'Bot'
|
||||
);
|
||||
|
||||
console.log(` Found ${appEvents.length} bot/app events in last 10 events`);
|
||||
appEvents.forEach((event: any) => {
|
||||
console.log(` - ${event.actor.login}: ${event.type} at ${event.created_at}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Summary and recommendations
|
||||
console.log("\n=== Analysis Summary ===");
|
||||
console.log("\nPossible causes for inconsistent 500 errors:");
|
||||
console.log("1. Race conditions with other bots (check events above)");
|
||||
console.log("2. Token scope varies based on who triggered the action");
|
||||
console.log("3. GitHub App needs to be reinstalled/reconfigured");
|
||||
console.log("4. Branch-specific protection rules");
|
||||
|
||||
} catch (error) {
|
||||
console.error("\nError during diagnosis:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const [token, owner, repo] = process.argv.slice(2);
|
||||
if (!token || !owner || !repo) {
|
||||
console.log("Usage: bun diagnose-app-permissions.ts <token> <owner> <repo>");
|
||||
console.log("\nThis script helps diagnose why GitHub App permissions are inconsistent.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
diagnosePermissions(token, owner, repo);
|
||||
112
revert-test-commit.ts
Normal file
112
revert-test-commit.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bun
|
||||
// Revert the test commit we just made
|
||||
|
||||
const GITHUB_API_URL = "https://api.github.com";
|
||||
|
||||
async function revertTestCommit(token: string) {
|
||||
const owner = "anthropics";
|
||||
const repo = "anthropic";
|
||||
const branch = "monty/fixing-pipeline-runner";
|
||||
|
||||
const headers = {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
};
|
||||
|
||||
console.log(`\n=== Reverting test commit ===`);
|
||||
console.log(`Repository: ${owner}/${repo}`);
|
||||
console.log(`Branch: ${branch}\n`);
|
||||
|
||||
try {
|
||||
// Get current branch state
|
||||
console.log("Getting current branch reference...");
|
||||
const refUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${branch}`;
|
||||
const refResponse = await fetch(refUrl, { headers });
|
||||
|
||||
if (!refResponse.ok) {
|
||||
console.error(`Failed to get branch: ${refResponse.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const refData = await refResponse.json();
|
||||
const currentSha = refData.object.sha;
|
||||
console.log(`Current branch SHA: ${currentSha}`);
|
||||
|
||||
// Get the current commit to find its parent
|
||||
console.log("\nGetting current commit details...");
|
||||
const commitResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/commits/${currentSha}`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (!commitResponse.ok) {
|
||||
console.error(`Failed to get commit: ${commitResponse.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const commitData = await commitResponse.json();
|
||||
console.log(`Current commit message: "${commitData.message}"`);
|
||||
|
||||
if (!commitData.message.includes("Debug: Test commit to reproduce 500 error")) {
|
||||
console.log("⚠️ Current commit doesn't look like our test commit");
|
||||
console.log("Are you sure you want to revert this?");
|
||||
console.log("Current message:", commitData.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (commitData.parents.length === 0) {
|
||||
console.error("Cannot revert: this appears to be the initial commit");
|
||||
return;
|
||||
}
|
||||
|
||||
const parentSha = commitData.parents[0].sha;
|
||||
console.log(`Parent SHA: ${parentSha}`);
|
||||
|
||||
// Reset the branch to the parent commit
|
||||
console.log("\nReverting branch to parent commit...");
|
||||
const updateRefResponse = await fetch(refUrl, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sha: parentSha,
|
||||
force: true, // Force is needed for this kind of reset
|
||||
}),
|
||||
});
|
||||
|
||||
if (!updateRefResponse.ok) {
|
||||
console.error(`Failed to revert: ${updateRefResponse.status}`);
|
||||
const error = await updateRefResponse.text();
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("✅ Successfully reverted test commit!");
|
||||
console.log(`Branch ${branch} is now back to SHA: ${parentSha}`);
|
||||
|
||||
// Verify the revert
|
||||
console.log("\nVerifying revert...");
|
||||
const verifyResponse = await fetch(refUrl, { headers });
|
||||
const verifyData = await verifyResponse.json();
|
||||
|
||||
if (verifyData.object.sha === parentSha) {
|
||||
console.log("✅ Revert confirmed");
|
||||
} else {
|
||||
console.log("⚠️ Unexpected SHA after revert:", verifyData.object.sha);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const token = process.argv[2];
|
||||
if (!token) {
|
||||
console.log("Usage: bun revert-test-commit.ts <github-pat>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
revertTestCommit(token);
|
||||
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -58,27 +58,10 @@ export function buildAllowedToolsString(
|
||||
|
||||
export function buildDisallowedToolsString(
|
||||
customDisallowedTools?: string,
|
||||
allowedTools?: string,
|
||||
): string {
|
||||
let disallowedTools = [...DISALLOWED_TOOLS];
|
||||
|
||||
// If user has explicitly allowed some hardcoded disallowed tools, remove them from disallowed list
|
||||
if (allowedTools) {
|
||||
const allowedToolsArray = allowedTools
|
||||
.split(",")
|
||||
.map((tool) => tool.trim());
|
||||
disallowedTools = disallowedTools.filter(
|
||||
(tool) => !allowedToolsArray.includes(tool),
|
||||
);
|
||||
}
|
||||
|
||||
let allDisallowedTools = disallowedTools.join(",");
|
||||
let allDisallowedTools = DISALLOWED_TOOLS.join(",");
|
||||
if (customDisallowedTools) {
|
||||
if (allDisallowedTools) {
|
||||
allDisallowedTools = `${allDisallowedTools},${customDisallowedTools}`;
|
||||
} else {
|
||||
allDisallowedTools = customDisallowedTools;
|
||||
}
|
||||
allDisallowedTools = `${allDisallowedTools},${customDisallowedTools}`;
|
||||
}
|
||||
return allDisallowedTools;
|
||||
}
|
||||
@@ -86,7 +69,7 @@ export function buildDisallowedToolsString(
|
||||
export function prepareContext(
|
||||
context: ParsedGitHubContext,
|
||||
claudeCommentId: string,
|
||||
baseBranch?: string,
|
||||
defaultBranch?: string,
|
||||
claudeBranch?: string,
|
||||
): PreparedContext {
|
||||
const repository = context.repository.full_name;
|
||||
@@ -164,7 +147,7 @@ export function prepareContext(
|
||||
...(commentId && { commentId }),
|
||||
commentBody,
|
||||
...(claudeBranch && { claudeBranch }),
|
||||
...(baseBranch && { baseBranch }),
|
||||
...(defaultBranch && { defaultBranch }),
|
||||
};
|
||||
break;
|
||||
|
||||
@@ -186,7 +169,7 @@ export function prepareContext(
|
||||
prNumber,
|
||||
commentBody,
|
||||
...(claudeBranch && { claudeBranch }),
|
||||
...(baseBranch && { baseBranch }),
|
||||
...(defaultBranch && { defaultBranch }),
|
||||
};
|
||||
break;
|
||||
|
||||
@@ -211,13 +194,13 @@ export function prepareContext(
|
||||
prNumber,
|
||||
commentBody,
|
||||
...(claudeBranch && { claudeBranch }),
|
||||
...(baseBranch && { baseBranch }),
|
||||
...(defaultBranch && { defaultBranch }),
|
||||
};
|
||||
break;
|
||||
} else if (!claudeBranch) {
|
||||
throw new Error("CLAUDE_BRANCH is required for issue_comment event");
|
||||
} else if (!baseBranch) {
|
||||
throw new Error("BASE_BRANCH is required for issue_comment event");
|
||||
} else if (!defaultBranch) {
|
||||
throw new Error("DEFAULT_BRANCH is required for issue_comment event");
|
||||
} else if (!issueNumber) {
|
||||
throw new Error(
|
||||
"ISSUE_NUMBER is required for issue_comment event for issues",
|
||||
@@ -229,7 +212,7 @@ export function prepareContext(
|
||||
commentId,
|
||||
isPR: false,
|
||||
claudeBranch: claudeBranch,
|
||||
baseBranch,
|
||||
defaultBranch,
|
||||
issueNumber,
|
||||
commentBody,
|
||||
};
|
||||
@@ -245,8 +228,8 @@ export function prepareContext(
|
||||
if (isPR) {
|
||||
throw new Error("IS_PR must be false for issues event");
|
||||
}
|
||||
if (!baseBranch) {
|
||||
throw new Error("BASE_BRANCH is required for issues event");
|
||||
if (!defaultBranch) {
|
||||
throw new Error("DEFAULT_BRANCH is required for issues event");
|
||||
}
|
||||
if (!claudeBranch) {
|
||||
throw new Error("CLAUDE_BRANCH is required for issues event");
|
||||
@@ -263,7 +246,7 @@ export function prepareContext(
|
||||
eventAction: "assigned",
|
||||
isPR: false,
|
||||
issueNumber,
|
||||
baseBranch,
|
||||
defaultBranch,
|
||||
claudeBranch,
|
||||
assigneeTrigger,
|
||||
};
|
||||
@@ -273,7 +256,7 @@ export function prepareContext(
|
||||
eventAction: "opened",
|
||||
isPR: false,
|
||||
issueNumber,
|
||||
baseBranch,
|
||||
defaultBranch,
|
||||
claudeBranch,
|
||||
};
|
||||
} else {
|
||||
@@ -294,7 +277,7 @@ export function prepareContext(
|
||||
isPR: true,
|
||||
prNumber,
|
||||
...(claudeBranch && { claudeBranch }),
|
||||
...(baseBranch && { baseBranch }),
|
||||
...(defaultBranch && { defaultBranch }),
|
||||
};
|
||||
break;
|
||||
|
||||
@@ -541,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.baseBranch}...<branch-name>?quick_pull=1&title=<url-encoded-title>&body=<url-encoded-body>)
|
||||
[Create a PR](${GITHUB_SERVER_URL}/${context.repository}/compare/${eventData.defaultBranch}...<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.baseBranch}'.
|
||||
- The target-branch should be '${eventData.defaultBranch}'.
|
||||
- The branch-name is the current branch: ${eventData.claudeBranch}
|
||||
- The body should include:
|
||||
- A clear description of the changes
|
||||
@@ -632,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,
|
||||
baseBranch: string | undefined,
|
||||
defaultBranch: string | undefined,
|
||||
claudeBranch: string | undefined,
|
||||
githubData: FetchDataResult,
|
||||
context: ParsedGitHubContext,
|
||||
@@ -641,7 +624,7 @@ export async function createPrompt(
|
||||
const preparedContext = prepareContext(
|
||||
context,
|
||||
claudeCommentId.toString(),
|
||||
baseBranch,
|
||||
defaultBranch,
|
||||
claudeBranch,
|
||||
);
|
||||
|
||||
@@ -665,7 +648,6 @@ export async function createPrompt(
|
||||
);
|
||||
const allDisallowedTools = buildDisallowedToolsString(
|
||||
preparedContext.disallowedTools,
|
||||
preparedContext.allowedTools,
|
||||
);
|
||||
|
||||
core.exportVariable("ALLOWED_TOOLS", allAllowedTools);
|
||||
|
||||
@@ -16,7 +16,7 @@ type PullRequestReviewCommentEvent = {
|
||||
commentId?: string; // May be present for review comments
|
||||
commentBody: string;
|
||||
claudeBranch?: string;
|
||||
baseBranch?: string;
|
||||
defaultBranch?: string;
|
||||
};
|
||||
|
||||
type PullRequestReviewEvent = {
|
||||
@@ -25,7 +25,7 @@ type PullRequestReviewEvent = {
|
||||
prNumber: string;
|
||||
commentBody: string;
|
||||
claudeBranch?: string;
|
||||
baseBranch?: string;
|
||||
defaultBranch?: string;
|
||||
};
|
||||
|
||||
type IssueCommentEvent = {
|
||||
@@ -33,7 +33,7 @@ type IssueCommentEvent = {
|
||||
commentId: string;
|
||||
issueNumber: string;
|
||||
isPR: false;
|
||||
baseBranch: string;
|
||||
defaultBranch: string;
|
||||
claudeBranch: string;
|
||||
commentBody: string;
|
||||
};
|
||||
@@ -46,7 +46,7 @@ type PullRequestCommentEvent = {
|
||||
isPR: true;
|
||||
commentBody: string;
|
||||
claudeBranch?: string;
|
||||
baseBranch?: string;
|
||||
defaultBranch?: string;
|
||||
};
|
||||
|
||||
type IssueOpenedEvent = {
|
||||
@@ -54,7 +54,7 @@ type IssueOpenedEvent = {
|
||||
eventAction: "opened";
|
||||
isPR: false;
|
||||
issueNumber: string;
|
||||
baseBranch: string;
|
||||
defaultBranch: string;
|
||||
claudeBranch: string;
|
||||
};
|
||||
|
||||
@@ -63,7 +63,7 @@ type IssueAssignedEvent = {
|
||||
eventAction: "assigned";
|
||||
isPR: false;
|
||||
issueNumber: string;
|
||||
baseBranch: string;
|
||||
defaultBranch: string;
|
||||
claudeBranch: string;
|
||||
assigneeTrigger: string;
|
||||
};
|
||||
@@ -74,7 +74,7 @@ type PullRequestEvent = {
|
||||
isPR: true;
|
||||
prNumber: string;
|
||||
claudeBranch?: string;
|
||||
baseBranch?: string;
|
||||
defaultBranch?: string;
|
||||
};
|
||||
|
||||
// Union type for all possible event types
|
||||
|
||||
@@ -77,7 +77,7 @@ async function run() {
|
||||
// Step 10: Create prompt file
|
||||
await createPrompt(
|
||||
commentId,
|
||||
branchInfo.baseBranch,
|
||||
branchInfo.defaultBranch,
|
||||
branchInfo.claudeBranch,
|
||||
githubData,
|
||||
context,
|
||||
@@ -92,10 +92,7 @@ async function run() {
|
||||
);
|
||||
core.setOutput("mcp_config", mcpConfig);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
core.setFailed(`Prepare step failed with error: ${errorMessage}`);
|
||||
// Also output the clean error message for the action to capture
|
||||
core.setOutput("prepare_error", errorMessage);
|
||||
core.setFailed(`Prepare step failed with error: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 baseBranch = process.env.BASE_BRANCH || "main";
|
||||
const defaultBranch = process.env.DEFAULT_BRANCH || "main";
|
||||
const triggerUsername = process.env.TRIGGER_USERNAME;
|
||||
|
||||
const context = parseGitHubContext();
|
||||
@@ -92,7 +92,7 @@ async function run() {
|
||||
owner,
|
||||
repo,
|
||||
claudeBranch,
|
||||
baseBranch,
|
||||
defaultBranch,
|
||||
);
|
||||
|
||||
// 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\\/${baseBranch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\.\\.`,
|
||||
`${serverUrlPattern}\\/.+\\/compare\\/${defaultBranch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\.\\.`,
|
||||
);
|
||||
const containsPRUrl = currentBody.match(prUrlPattern);
|
||||
|
||||
@@ -113,7 +113,7 @@ async function run() {
|
||||
await octokit.rest.repos.compareCommitsWithBasehead({
|
||||
owner,
|
||||
repo,
|
||||
basehead: `${baseBranch}...${claudeBranch}`,
|
||||
basehead: `${defaultBranch}...${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/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
||||
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${defaultBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
||||
prLink = `\n[Create a PR](${prUrl})`;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -145,48 +145,38 @@ async function run() {
|
||||
duration_api_ms?: number;
|
||||
} | null = null;
|
||||
let actionFailed = false;
|
||||
let errorDetails: string | undefined;
|
||||
|
||||
// First check if prepare step failed
|
||||
const prepareSuccess = process.env.PREPARE_SUCCESS !== "false";
|
||||
const prepareError = process.env.PREPARE_ERROR;
|
||||
// Check for existence of output file and parse it if available
|
||||
try {
|
||||
const outputFile = process.env.OUTPUT_FILE;
|
||||
if (outputFile) {
|
||||
const fileContent = await fs.readFile(outputFile, "utf8");
|
||||
const outputData = JSON.parse(fileContent);
|
||||
|
||||
if (!prepareSuccess && prepareError) {
|
||||
actionFailed = true;
|
||||
errorDetails = prepareError;
|
||||
} else {
|
||||
// Check for existence of output file and parse it if available
|
||||
try {
|
||||
const outputFile = process.env.OUTPUT_FILE;
|
||||
if (outputFile) {
|
||||
const fileContent = await fs.readFile(outputFile, "utf8");
|
||||
const outputData = JSON.parse(fileContent);
|
||||
|
||||
// Output file is an array, get the last element which contains execution details
|
||||
if (Array.isArray(outputData) && outputData.length > 0) {
|
||||
const lastElement = outputData[outputData.length - 1];
|
||||
if (
|
||||
lastElement.role === "system" &&
|
||||
"cost_usd" in lastElement &&
|
||||
"duration_ms" in lastElement
|
||||
) {
|
||||
executionDetails = {
|
||||
cost_usd: lastElement.cost_usd,
|
||||
duration_ms: lastElement.duration_ms,
|
||||
duration_api_ms: lastElement.duration_api_ms,
|
||||
};
|
||||
}
|
||||
// Output file is an array, get the last element which contains execution details
|
||||
if (Array.isArray(outputData) && outputData.length > 0) {
|
||||
const lastElement = outputData[outputData.length - 1];
|
||||
if (
|
||||
lastElement.role === "system" &&
|
||||
"cost_usd" in lastElement &&
|
||||
"duration_ms" in lastElement
|
||||
) {
|
||||
executionDetails = {
|
||||
cost_usd: lastElement.cost_usd,
|
||||
duration_ms: lastElement.duration_ms,
|
||||
duration_api_ms: lastElement.duration_api_ms,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the Claude action failed
|
||||
const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false";
|
||||
actionFailed = !claudeSuccess;
|
||||
} catch (error) {
|
||||
console.error("Error reading output file:", error);
|
||||
// If we can't read the file, check for any failure markers
|
||||
actionFailed = process.env.CLAUDE_SUCCESS === "false";
|
||||
}
|
||||
|
||||
// Check if the action failed by looking at the exit code or error marker
|
||||
const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false";
|
||||
actionFailed = !claudeSuccess;
|
||||
} catch (error) {
|
||||
console.error("Error reading output file:", error);
|
||||
// If we can't read the file, check for any failure markers
|
||||
actionFailed = process.env.CLAUDE_SUCCESS === "false";
|
||||
}
|
||||
|
||||
// Prepare input for updateCommentBody function
|
||||
@@ -199,7 +189,6 @@ async function run() {
|
||||
prLink,
|
||||
branchName: shouldDeleteBranch ? undefined : claudeBranch,
|
||||
triggerUsername,
|
||||
errorDetails,
|
||||
};
|
||||
|
||||
const updatedBody = updateCommentBody(commentInput);
|
||||
|
||||
@@ -32,7 +32,6 @@ export type ParsedGitHubContext = {
|
||||
disallowedTools: string;
|
||||
customInstructions: string;
|
||||
directPrompt: string;
|
||||
baseBranch?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -56,7 +55,6 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
||||
disallowedTools: process.env.DISALLOWED_TOOLS ?? "",
|
||||
customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "",
|
||||
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
||||
baseBranch: process.env.BASE_BRANCH,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export async function checkAndDeleteEmptyBranch(
|
||||
owner: string,
|
||||
repo: string,
|
||||
claudeBranch: string | undefined,
|
||||
baseBranch: string,
|
||||
defaultBranch: 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: `${baseBranch}...${claudeBranch}`,
|
||||
basehead: `${defaultBranch}...${claudeBranch}`,
|
||||
});
|
||||
|
||||
// If there are no commits, mark branch for deletion
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { Octokits } from "../api/client";
|
||||
import type { FetchDataResult } from "../data/fetcher";
|
||||
|
||||
export type BranchInfo = {
|
||||
baseBranch: string;
|
||||
defaultBranch: string;
|
||||
claudeBranch?: string;
|
||||
currentBranch: string;
|
||||
};
|
||||
@@ -26,9 +26,15 @@ 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;
|
||||
@@ -36,7 +42,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 source...`,
|
||||
`PR #${entityNumber} is ${prState}, creating new branch from default...`,
|
||||
);
|
||||
// Fall through to create a new branch like we do for issues
|
||||
} else {
|
||||
@@ -45,43 +51,23 @@ 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, we need to get the base branch of the PR
|
||||
const baseBranch = prData.baseRefName;
|
||||
|
||||
// For open PRs, return branch info
|
||||
return {
|
||||
baseBranch,
|
||||
defaultBranch,
|
||||
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} from source branch: ${sourceBranch}...`,
|
||||
);
|
||||
console.log(`Creating new branch for ${entityType} #${entityNumber}...`);
|
||||
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
@@ -93,14 +79,14 @@ export async function setupBranch(
|
||||
const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
|
||||
|
||||
try {
|
||||
// Get the SHA of the source branch
|
||||
const sourceBranchRef = await octokits.rest.git.getRef({
|
||||
// Get the SHA of the default branch
|
||||
const defaultBranchRef = await octokits.rest.git.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `heads/${sourceBranch}`,
|
||||
ref: `heads/${defaultBranch}`,
|
||||
});
|
||||
|
||||
const currentSHA = sourceBranchRef.data.object.sha;
|
||||
const currentSHA = defaultBranchRef.data.object.sha;
|
||||
|
||||
console.log(`Current SHA: ${currentSHA}`);
|
||||
|
||||
@@ -112,8 +98,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(
|
||||
@@ -122,9 +108,9 @@ export async function setupBranch(
|
||||
|
||||
// Set outputs for GitHub Actions
|
||||
core.setOutput("CLAUDE_BRANCH", newBranch);
|
||||
core.setOutput("BASE_BRANCH", sourceBranch);
|
||||
core.setOutput("DEFAULT_BRANCH", defaultBranch);
|
||||
return {
|
||||
baseBranch: sourceBranch,
|
||||
defaultBranch,
|
||||
claudeBranch: newBranch,
|
||||
currentBranch: newBranch,
|
||||
};
|
||||
|
||||
@@ -15,7 +15,6 @@ export type CommentUpdateInput = {
|
||||
prLink?: string;
|
||||
branchName?: string;
|
||||
triggerUsername?: string;
|
||||
errorDetails?: string;
|
||||
};
|
||||
|
||||
export function ensureProperlyEncodedUrl(url: string): string | null {
|
||||
@@ -76,7 +75,6 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
||||
actionFailed,
|
||||
branchName,
|
||||
triggerUsername,
|
||||
errorDetails,
|
||||
} = input;
|
||||
|
||||
// Extract content from the original comment body
|
||||
@@ -179,14 +177,7 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
||||
}
|
||||
|
||||
// Build the new body with blank line between header and separator
|
||||
let newBody = `${header}${links}`;
|
||||
|
||||
// Add error details if available
|
||||
if (actionFailed && errorDetails) {
|
||||
newBody += `\n\n\`\`\`\n${errorDetails}\n\`\`\``;
|
||||
}
|
||||
|
||||
newBody += `\n\n---\n`;
|
||||
let newBody = `${header}${links}\n\n---\n`;
|
||||
|
||||
// Clean up the body content
|
||||
// Remove any existing View job run, branch links from the bottom
|
||||
|
||||
@@ -39,19 +39,25 @@ async function retryWithBackoff<T>(
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`Operation failed after ${maxAttempts} attempts`);
|
||||
throw lastError;
|
||||
throw new Error(
|
||||
`Operation failed after ${maxAttempts} attempts. Last error: ${
|
||||
lastError?.message ?? "Unknown error"
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function getOidcToken(): Promise<string> {
|
||||
try {
|
||||
const oidcToken = await core.getIDToken("claude-code-github-action");
|
||||
|
||||
if (!oidcToken) {
|
||||
throw new Error("OIDC token not found");
|
||||
}
|
||||
|
||||
return oidcToken;
|
||||
} catch (error) {
|
||||
console.error("Failed to get OIDC token:", error);
|
||||
throw new Error(
|
||||
"Could not fetch an OIDC token. Did you remember to add `id-token: write` to your workflow permissions?",
|
||||
`Failed to get OIDC token: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -68,15 +74,9 @@ async function exchangeForAppToken(oidcToken: string): Promise<string> {
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const responseJson = (await response.json()) as {
|
||||
error?: {
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
console.error(
|
||||
`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson?.error?.message ?? "Unknown error"}`,
|
||||
throw new Error(
|
||||
`App token exchange failed: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
throw new Error(`${responseJson?.error?.message ?? "Unknown error"}`);
|
||||
}
|
||||
|
||||
const appTokenData = (await response.json()) as {
|
||||
@@ -117,9 +117,7 @@ export async function setupGitHubToken(): Promise<string> {
|
||||
core.setOutput("GITHUB_TOKEN", appToken);
|
||||
return appToken;
|
||||
} catch (error) {
|
||||
core.setFailed(
|
||||
`Failed to setup GitHub token: ${error}.\n\nIf you instead wish to use this action with a custom GitHub token or custom GitHub app, provide a \`github_token\` in the \`uses\` section of the app in your workflow yml file.`,
|
||||
);
|
||||
core.setFailed(`Failed to setup GitHub token: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
// GitHub File Operations MCP Server
|
||||
// GitHub File Operations MCP Server - Enhanced with detailed error logging
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
import { readFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import fetch from "node-fetch";
|
||||
import { GITHUB_API_URL } from "../github/api/config";
|
||||
// Import removed - define inline to ensure subprocess gets the value
|
||||
const GITHUB_API_URL = process.env.GITHUB_API_URL || "https://api.github.com";
|
||||
|
||||
type GitHubRef = {
|
||||
object: {
|
||||
@@ -51,6 +52,18 @@ const server = new McpServer({
|
||||
version: "0.0.1",
|
||||
});
|
||||
|
||||
// Enhanced error logging helper
|
||||
function logDetailedError(prefix: string, error: any) {
|
||||
console.error(`[${prefix}] FULL ERROR CAUGHT:`, error);
|
||||
console.error(`[${prefix}] Error type:`, typeof error);
|
||||
console.error(`[${prefix}] Error constructor:`, error?.constructor?.name);
|
||||
console.error(`[${prefix}] Error stack:`, error instanceof Error ? error.stack : 'No stack trace');
|
||||
if (error && typeof error === 'object') {
|
||||
console.error(`[${prefix}] Error properties:`, Object.keys(error));
|
||||
console.error(`[${prefix}] Error JSON:`, JSON.stringify(error, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
// Commit files tool
|
||||
server.tool(
|
||||
"commit_files",
|
||||
@@ -67,6 +80,19 @@ server.tool(
|
||||
const owner = REPO_OWNER;
|
||||
const repo = REPO_NAME;
|
||||
const branch = BRANCH_NAME;
|
||||
console.error(`[commit_files] Starting commit for ${files.length} files to ${owner}/${repo}:${branch}`);
|
||||
console.error(`[commit_files] REPO_DIR: ${REPO_DIR}`);
|
||||
console.error(`[commit_files] Input files:`, files);
|
||||
console.error(`[commit_files] Environment check:`, {
|
||||
GITHUB_TOKEN: process.env.GITHUB_TOKEN ? 'Present' : 'Missing',
|
||||
REPO_OWNER,
|
||||
REPO_NAME,
|
||||
BRANCH_NAME,
|
||||
REPO_DIR,
|
||||
GITHUB_API_URL,
|
||||
CWD: process.cwd(),
|
||||
});
|
||||
|
||||
try {
|
||||
const githubToken = process.env.GITHUB_TOKEN;
|
||||
if (!githubToken) {
|
||||
@@ -123,7 +149,12 @@ server.tool(
|
||||
? filePath
|
||||
: join(REPO_DIR, filePath);
|
||||
|
||||
const content = await readFile(fullPath, "utf-8");
|
||||
console.error(`[commit_files] Reading file: ${fullPath}`);
|
||||
const content = await readFile(fullPath, "utf-8").catch((error) => {
|
||||
console.error(`[commit_files] Failed to read file '${fullPath}':`, error);
|
||||
throw new Error(`Failed to read file '${fullPath}': ${error.message || error}`);
|
||||
});
|
||||
console.error(`[commit_files] Successfully read file: ${fullPath} (${content.length} chars)`);
|
||||
return {
|
||||
path: filePath,
|
||||
mode: "100644",
|
||||
@@ -186,24 +217,158 @@ server.tool(
|
||||
|
||||
// 6. Update the reference to point to the new commit
|
||||
const updateRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${branch}`;
|
||||
const updateRefResponse = await fetch(updateRefUrl, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${githubToken}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sha: newCommitData.sha,
|
||||
force: false,
|
||||
}),
|
||||
console.error(`[commit_files] Updating reference: ${updateRefUrl}`);
|
||||
console.error(`[commit_files] New commit SHA: ${newCommitData.sha}`);
|
||||
console.error(`[commit_files] Base SHA was: ${baseSha}`);
|
||||
|
||||
// Log full request context before making the request
|
||||
const requestBody = JSON.stringify({
|
||||
sha: newCommitData.sha,
|
||||
force: false,
|
||||
});
|
||||
const requestHeaders = {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${githubToken}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
console.error(`[commit_files] Full request details:`, {
|
||||
url: updateRefUrl,
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...requestHeaders,
|
||||
Authorization: `Bearer [TOKEN_LENGTH:${githubToken?.length || 0}]`,
|
||||
},
|
||||
body: requestBody,
|
||||
timestamp: new Date().toISOString(),
|
||||
environment: {
|
||||
NODE_VERSION: process.version,
|
||||
PLATFORM: process.platform,
|
||||
ARCH: process.arch,
|
||||
},
|
||||
previousOperations: {
|
||||
treeCreated: treeData?.sha ? 'YES' : 'NO',
|
||||
commitCreated: newCommitData?.sha ? 'YES' : 'NO',
|
||||
treeSha: treeData?.sha,
|
||||
commitSha: newCommitData?.sha,
|
||||
baseSha: baseSha,
|
||||
}
|
||||
});
|
||||
|
||||
// Log memory usage before request
|
||||
const memoryBefore = process.memoryUsage();
|
||||
console.error(`[commit_files] Memory before request:`, {
|
||||
rss: `${(memoryBefore.rss / 1024 / 1024).toFixed(2)} MB`,
|
||||
heapUsed: `${(memoryBefore.heapUsed / 1024 / 1024).toFixed(2)} MB`,
|
||||
});
|
||||
|
||||
let updateRefResponse;
|
||||
const requestStartTime = Date.now();
|
||||
|
||||
try {
|
||||
updateRefResponse = await fetch(updateRefUrl, {
|
||||
method: "PATCH",
|
||||
headers: requestHeaders,
|
||||
body: requestBody,
|
||||
});
|
||||
} catch (fetchError) {
|
||||
const requestDuration = Date.now() - requestStartTime;
|
||||
console.error(`[commit_files] FETCH ERROR during reference update after ${requestDuration}ms:`, fetchError);
|
||||
logDetailedError('commit_files_fetch', fetchError);
|
||||
throw new Error(`Network error during reference update after ${requestDuration}ms: ${fetchError?.message || 'Unknown fetch error'}`);
|
||||
}
|
||||
|
||||
const requestDuration = Date.now() - requestStartTime;
|
||||
console.error(`[commit_files] Request completed in ${requestDuration}ms`);
|
||||
console.error(`[commit_files] Response received at: ${new Date().toISOString()}`);
|
||||
|
||||
console.error(`[commit_files] Update reference response status: ${updateRefResponse.status}`);
|
||||
console.error(`[commit_files] Response headers:`, Object.fromEntries(updateRefResponse.headers.entries()));
|
||||
|
||||
// Log specific important headers
|
||||
console.error(`[commit_files] Key response headers:`, {
|
||||
'x-github-request-id': updateRefResponse.headers.get('x-github-request-id'),
|
||||
'x-ratelimit-remaining': updateRefResponse.headers.get('x-ratelimit-remaining'),
|
||||
'x-ratelimit-reset': updateRefResponse.headers.get('x-ratelimit-reset'),
|
||||
'content-type': updateRefResponse.headers.get('content-type'),
|
||||
'content-length': updateRefResponse.headers.get('content-length'),
|
||||
'server': updateRefResponse.headers.get('server'),
|
||||
});
|
||||
|
||||
if (!updateRefResponse.ok) {
|
||||
const errorText = await updateRefResponse.text();
|
||||
console.error(`[commit_files] ERROR RESPONSE - Status: ${updateRefResponse.status} ${updateRefResponse.statusText}`);
|
||||
|
||||
// Capture the entire raw response body
|
||||
let responseArrayBuffer;
|
||||
let responseText = '';
|
||||
let responseHex = '';
|
||||
let responseBase64 = '';
|
||||
|
||||
try {
|
||||
// Clone the response so we can read it multiple ways
|
||||
const clonedResponse = updateRefResponse.clone();
|
||||
|
||||
// Get raw bytes
|
||||
responseArrayBuffer = await updateRefResponse.arrayBuffer();
|
||||
const responseBytes = new Uint8Array(responseArrayBuffer);
|
||||
|
||||
// Convert to text (with error handling for non-UTF8)
|
||||
responseText = new TextDecoder('utf-8', { fatal: false }).decode(responseBytes);
|
||||
|
||||
// Convert to hex for debugging binary responses
|
||||
responseHex = Array.from(responseBytes.slice(0, 1000)) // First 1000 bytes to avoid huge logs
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join(' ');
|
||||
|
||||
// Convert to base64
|
||||
responseBase64 = Buffer.from(responseBytes).toString('base64');
|
||||
|
||||
console.error(`[commit_files] COMPLETE ERROR RESPONSE:`);
|
||||
console.error(`[commit_files] ===== RESPONSE BODY (TEXT) =====`);
|
||||
console.error(responseText);
|
||||
console.error(`[commit_files] ===== END RESPONSE BODY =====`);
|
||||
console.error(`[commit_files] Response body length: ${responseBytes.length} bytes`);
|
||||
console.error(`[commit_files] Response body (first 1000 bytes as hex): ${responseHex}${responseBytes.length > 1000 ? '...' : ''}`);
|
||||
console.error(`[commit_files] Response body (base64): ${responseBase64}`);
|
||||
|
||||
// Try to parse as JSON if it looks like JSON
|
||||
if (responseText.trim().startsWith('{') || responseText.trim().startsWith('[')) {
|
||||
try {
|
||||
const parsedError = JSON.parse(responseText);
|
||||
console.error(`[commit_files] Parsed error object:`, JSON.stringify(parsedError, null, 2));
|
||||
} catch (e) {
|
||||
console.error(`[commit_files] Response looks like JSON but failed to parse:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (readError) {
|
||||
console.error(`[commit_files] CRITICAL: Failed to read error response:`, readError);
|
||||
logDetailedError('commit_files_response_read', readError);
|
||||
responseText = `Failed to read response: ${readError}`;
|
||||
}
|
||||
|
||||
// Log memory state after error
|
||||
const memoryAfter = process.memoryUsage();
|
||||
console.error(`[commit_files] Memory after error:`, {
|
||||
rss: `${(memoryAfter.rss / 1024 / 1024).toFixed(2)} MB`,
|
||||
heapUsed: `${(memoryAfter.heapUsed / 1024 / 1024).toFixed(2)} MB`,
|
||||
});
|
||||
|
||||
// Special handling for 500 errors
|
||||
if (updateRefResponse.status === 500) {
|
||||
const requestId = updateRefResponse.headers.get('x-github-request-id');
|
||||
console.error(`[commit_files] ===== GITHUB 500 ERROR DETAILS =====`);
|
||||
console.error(`[commit_files] GitHub Request ID: ${requestId}`);
|
||||
console.error(`[commit_files] This is an internal GitHub server error`);
|
||||
console.error(`[commit_files] The error may be transient - consider retrying`);
|
||||
console.error(`[commit_files] Note: Tree (${treeData?.sha}) and commit (${newCommitData?.sha}) were created successfully`);
|
||||
console.error(`[commit_files] Only the reference update failed`);
|
||||
console.error(`[commit_files] ===================================`);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to update reference: ${updateRefResponse.status} - ${errorText}`,
|
||||
`Failed to update reference: ${updateRefResponse.status} - ${responseText || 'No response body'}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -229,18 +394,21 @@ server.tool(
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
logDetailedError('commit_files', error);
|
||||
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${errorMessage}`,
|
||||
},
|
||||
],
|
||||
error: errorMessage,
|
||||
isError: true,
|
||||
};
|
||||
console.error(`[commit_files] Final error message being thrown: "${errorMessage}"`);
|
||||
|
||||
// Ensure we're throwing a proper Error object with a message
|
||||
if (!errorMessage || errorMessage === 'undefined' || errorMessage === '[object Object]') {
|
||||
console.error(`[commit_files] WARNING: Error message is undefined or object, using fallback`);
|
||||
const fallbackMessage = error instanceof Error && error.stack
|
||||
? `Failed to commit files: ${error.stack.split('\n')[0]}`
|
||||
: 'Failed to commit files: Unknown error occurred';
|
||||
throw new Error(fallbackMessage);
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -380,22 +548,71 @@ server.tool(
|
||||
|
||||
// 6. Update the reference to point to the new commit
|
||||
const updateRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${branch}`;
|
||||
const updateRefResponse = await fetch(updateRefUrl, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${githubToken}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sha: newCommitData.sha,
|
||||
force: false,
|
||||
}),
|
||||
});
|
||||
console.error(`[delete_files] Updating reference: ${updateRefUrl}`);
|
||||
console.error(`[delete_files] New commit SHA: ${newCommitData.sha}`);
|
||||
console.error(`[delete_files] Base SHA was: ${baseSha}`);
|
||||
console.error(`[delete_files] Request body:`, JSON.stringify({
|
||||
sha: newCommitData.sha,
|
||||
force: false,
|
||||
}));
|
||||
|
||||
let updateRefResponse;
|
||||
try {
|
||||
updateRefResponse = await fetch(updateRefUrl, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${githubToken}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sha: newCommitData.sha,
|
||||
force: false,
|
||||
}),
|
||||
});
|
||||
} catch (fetchError) {
|
||||
console.error(`[delete_files] FETCH ERROR during reference update:`, fetchError);
|
||||
logDetailedError('delete_files_fetch', fetchError);
|
||||
throw new Error(`Network error during reference update: ${fetchError?.message || 'Unknown fetch error'}`);
|
||||
}
|
||||
|
||||
console.error(`[delete_files] Update reference response status: ${updateRefResponse.status}`);
|
||||
console.error(`[delete_files] Response headers:`, Object.fromEntries(updateRefResponse.headers.entries()));
|
||||
|
||||
if (!updateRefResponse.ok) {
|
||||
const errorText = await updateRefResponse.text();
|
||||
let errorText;
|
||||
try {
|
||||
errorText = await updateRefResponse.text();
|
||||
} catch (textError) {
|
||||
console.error(`[delete_files] Failed to read error response text:`, textError);
|
||||
errorText = 'Unable to read error response';
|
||||
}
|
||||
|
||||
console.error(`[delete_files] Update reference error body: "${errorText}"`);
|
||||
console.error(`[delete_files] Error body length: ${errorText?.length}`);
|
||||
console.error(`[delete_files] Error body type: ${typeof errorText}`);
|
||||
|
||||
// Log additional debugging info for 500 errors
|
||||
if (updateRefResponse.status === 500) {
|
||||
const requestId = updateRefResponse.headers.get('x-github-request-id');
|
||||
console.error(`[delete_files] GitHub Request ID: ${requestId}`);
|
||||
console.error(`[delete_files] This appears to be an internal GitHub error`);
|
||||
console.error(`[delete_files] Token was valid for tree/commit creation but failed for ref update`);
|
||||
console.error(`[delete_files] Branch protection rules or permissions might be an issue`);
|
||||
}
|
||||
|
||||
// Parse error if it's JSON
|
||||
let parsedError;
|
||||
try {
|
||||
if (errorText && errorText.trim().startsWith('{')) {
|
||||
parsedError = JSON.parse(errorText);
|
||||
console.error(`[delete_files] Parsed error:`, parsedError);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[delete_files] Error text is not JSON`);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to update reference: ${updateRefResponse.status} - ${errorText}`,
|
||||
);
|
||||
@@ -423,18 +640,21 @@ server.tool(
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
logDetailedError('delete_files', error);
|
||||
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${errorMessage}`,
|
||||
},
|
||||
],
|
||||
error: errorMessage,
|
||||
isError: true,
|
||||
};
|
||||
console.error(`[delete_files] Final error message being thrown: "${errorMessage}"`);
|
||||
|
||||
// Ensure we're throwing a proper Error object with a message
|
||||
if (!errorMessage || errorMessage === 'undefined' || errorMessage === '[object Object]') {
|
||||
console.error(`[delete_files] WARNING: Error message is undefined or object, using fallback`);
|
||||
const fallbackMessage = error instanceof Error && error.stack
|
||||
? `Failed to delete files: ${error.stack.split('\n')[0]}`
|
||||
: 'Failed to delete files: Unknown error occurred';
|
||||
throw new Error(fallbackMessage);
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -447,4 +667,4 @@ async function runServer() {
|
||||
});
|
||||
}
|
||||
|
||||
runServer().catch(console.error);
|
||||
runServer().catch(console.error);
|
||||
@@ -35,6 +35,7 @@ export async function prepareMcpConfig(
|
||||
REPO_NAME: repo,
|
||||
BRANCH_NAME: branch,
|
||||
REPO_DIR: process.env.GITHUB_WORKSPACE || process.cwd(),
|
||||
GITHUB_API_URL: process.env.GITHUB_API_URL || "https://api.github.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
261
test-with-new-branch.ts
Normal file
261
test-with-new-branch.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
#!/usr/bin/env bun
|
||||
// Test script that creates a new branch to test the commit_files flow
|
||||
// Run with: bun test-with-new-branch.ts <github-pat> <owner> <repo>
|
||||
|
||||
const GITHUB_API_URL = "https://api.github.com";
|
||||
|
||||
async function testCommitFilesWithNewBranch(token: string, owner: string, repo: string) {
|
||||
const headers = {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
};
|
||||
|
||||
// Create a unique branch name for testing
|
||||
const timestamp = Date.now();
|
||||
const testBranch = `claude-debug-500-test-${timestamp}`;
|
||||
|
||||
console.log(`\n=== Testing commit_files flow ===`);
|
||||
console.log(`Repository: ${owner}/${repo}`);
|
||||
console.log(`Test branch: ${testBranch}\n`);
|
||||
|
||||
try {
|
||||
// First, get the default branch to branch from
|
||||
console.log("Getting repository info...");
|
||||
const repoResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (!repoResponse.ok) {
|
||||
console.error(`Cannot access repository: ${repoResponse.status}`);
|
||||
const error = await repoResponse.text();
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const repoData = await repoResponse.json();
|
||||
const defaultBranch = repoData.default_branch;
|
||||
console.log(`✓ Default branch: ${defaultBranch}`);
|
||||
|
||||
// Get the SHA of the default branch
|
||||
console.log(`\nGetting ${defaultBranch} branch SHA...`);
|
||||
const defaultBranchResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${defaultBranch}`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (!defaultBranchResponse.ok) {
|
||||
console.error(`Cannot get default branch: ${defaultBranchResponse.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultBranchData = await defaultBranchResponse.json();
|
||||
const baseSha = defaultBranchData.object.sha;
|
||||
console.log(`✓ Base SHA: ${baseSha}`);
|
||||
|
||||
// Create a new branch
|
||||
console.log(`\nCreating test branch: ${testBranch}...`);
|
||||
const createBranchResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ref: `refs/heads/${testBranch}`,
|
||||
sha: baseSha,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!createBranchResponse.ok) {
|
||||
console.error(`Failed to create branch: ${createBranchResponse.status}`);
|
||||
const error = await createBranchResponse.text();
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✓ Created test branch: ${testBranch}`);
|
||||
|
||||
// Now replicate the commit_files flow
|
||||
console.log("\n--- Starting commit_files flow ---");
|
||||
|
||||
// Step 1: Get the branch reference (should be same as baseSha)
|
||||
console.log("\nStep 1: Getting branch reference...");
|
||||
const refUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${testBranch}`;
|
||||
const refResponse = await fetch(refUrl, { headers });
|
||||
|
||||
if (!refResponse.ok) {
|
||||
console.error(`Failed: ${refResponse.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const refData = await refResponse.json();
|
||||
console.log(`✓ Branch SHA: ${refData.object.sha}`);
|
||||
|
||||
// Step 2: Get the base commit
|
||||
console.log("\nStep 2: Getting base commit...");
|
||||
const commitUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/commits/${baseSha}`;
|
||||
const commitResponse = await fetch(commitUrl, { headers });
|
||||
|
||||
if (!commitResponse.ok) {
|
||||
console.error(`Failed: ${commitResponse.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const commitData = await commitResponse.json();
|
||||
const baseTreeSha = commitData.tree.sha;
|
||||
console.log(`✓ Base tree SHA: ${baseTreeSha}`);
|
||||
|
||||
// Step 3: Create a new tree
|
||||
console.log("\nStep 3: Creating new tree...");
|
||||
const treeUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/trees`;
|
||||
|
||||
const testFileContent = `# Test file for debugging 500 error
|
||||
# Created at: ${new Date().toISOString()}
|
||||
# This simulates the commit_files operation from claude-code-action
|
||||
|
||||
def test_function():
|
||||
# This simulates fixing a code issue
|
||||
result = "Fixed code"
|
||||
return result
|
||||
`;
|
||||
|
||||
const treeBody = {
|
||||
base_tree: baseTreeSha,
|
||||
tree: [{
|
||||
path: "test-debug-500.py",
|
||||
mode: "100644",
|
||||
type: "blob",
|
||||
content: testFileContent,
|
||||
}],
|
||||
};
|
||||
|
||||
const treeResponse = await fetch(treeUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(treeBody),
|
||||
});
|
||||
|
||||
if (!treeResponse.ok) {
|
||||
console.error(`Failed to create tree: ${treeResponse.status}`);
|
||||
const error = await treeResponse.text();
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const treeData = await treeResponse.json();
|
||||
console.log(`✓ Tree SHA: ${treeData.sha}`);
|
||||
|
||||
// Step 4: Create commit
|
||||
console.log("\nStep 4: Creating commit...");
|
||||
const newCommitUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/commits`;
|
||||
const commitBody = {
|
||||
message: "Test: Debugging 500 error in commit_files",
|
||||
tree: treeData.sha,
|
||||
parents: [baseSha],
|
||||
};
|
||||
|
||||
const newCommitResponse = await fetch(newCommitUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(commitBody),
|
||||
});
|
||||
|
||||
if (!newCommitResponse.ok) {
|
||||
console.error(`Failed to create commit: ${newCommitResponse.status}`);
|
||||
const error = await newCommitResponse.text();
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const newCommitData = await newCommitResponse.json();
|
||||
console.log(`✓ Commit SHA: ${newCommitData.sha}`);
|
||||
|
||||
// Step 5: Update reference (this is where the 500 error happens)
|
||||
console.log("\nStep 5: Updating branch reference (the critical step)...");
|
||||
const updateRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${testBranch}`;
|
||||
const updateBody = {
|
||||
sha: newCommitData.sha,
|
||||
force: false,
|
||||
};
|
||||
|
||||
console.log(`URL: PATCH ${updateRefUrl}`);
|
||||
console.log(`Body: ${JSON.stringify(updateBody)}`);
|
||||
|
||||
const startTime = Date.now();
|
||||
const updateRefResponse = await fetch(updateRefUrl, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(updateBody),
|
||||
});
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
console.log(`\nStatus: ${updateRefResponse.status} (took ${duration}ms)`);
|
||||
console.log(`Headers:`, {
|
||||
'x-ratelimit-remaining': updateRefResponse.headers.get('x-ratelimit-remaining'),
|
||||
'x-github-request-id': updateRefResponse.headers.get('x-github-request-id'),
|
||||
});
|
||||
|
||||
if (!updateRefResponse.ok) {
|
||||
console.error(`\n✗ FAILED: ${updateRefResponse.status}`);
|
||||
const errorText = await updateRefResponse.text();
|
||||
console.error(`Error body: "${errorText}"`);
|
||||
|
||||
if (updateRefResponse.status === 500) {
|
||||
console.error(`\n🔍 500 ERROR REPRODUCED!`);
|
||||
console.error(`This confirms the issue exists with PAT as well.`);
|
||||
console.error(`GitHub Request ID: ${updateRefResponse.headers.get('x-github-request-id')}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`\n✓ SUCCESS: Branch updated!`);
|
||||
console.log(`The 500 error might be specific to certain conditions.`);
|
||||
|
||||
// Cleanup: delete the test branch
|
||||
console.log(`\nCleaning up test branch...`);
|
||||
const deleteResponse = await fetch(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${testBranch}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
if (deleteResponse.ok) {
|
||||
console.log(`✓ Test branch deleted`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`\nUnexpected error:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Main execution
|
||||
const [token, owner, repo] = process.argv.slice(2);
|
||||
|
||||
if (!token || !owner || !repo) {
|
||||
console.log("Usage: bun test-with-new-branch.ts <github-pat> <owner> <repo>");
|
||||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" bun test-with-new-branch.ts ghp_xxx myorg myrepo");
|
||||
console.log(" bun test-with-new-branch.ts ghp_xxx anthropics anthropic");
|
||||
console.log("");
|
||||
console.log("This creates a test branch and replicates the commit_files flow.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Starting test with new branch...");
|
||||
testCommitFilesWithNewBranch(token, owner, repo);
|
||||
@@ -39,25 +39,6 @@ describe("updateCommentBody", () => {
|
||||
expect(result).toContain("**Claude encountered an error after 45s**");
|
||||
});
|
||||
|
||||
it("includes error details when provided", () => {
|
||||
const input = {
|
||||
...baseInput,
|
||||
currentBody: "Claude Code is working...",
|
||||
actionFailed: true,
|
||||
executionDetails: { duration_ms: 45000 },
|
||||
errorDetails: "Failed to fetch issue data",
|
||||
};
|
||||
|
||||
const result = updateCommentBody(input);
|
||||
expect(result).toContain("**Claude encountered an error after 45s**");
|
||||
expect(result).toContain("[View job]");
|
||||
expect(result).toContain("```\nFailed to fetch issue data\n```");
|
||||
// Ensure error details come after the header/links
|
||||
const errorIndex = result.indexOf("```");
|
||||
const headerIndex = result.indexOf("**Claude encountered an error");
|
||||
expect(errorIndex).toBeGreaterThan(headerIndex);
|
||||
});
|
||||
|
||||
it("handles username extraction from content when not provided", () => {
|
||||
const input = {
|
||||
...baseInput,
|
||||
|
||||
@@ -127,7 +127,7 @@ describe("generatePrompt", () => {
|
||||
eventName: "issue_comment",
|
||||
commentId: "67890",
|
||||
isPR: false,
|
||||
baseBranch: "main",
|
||||
defaultBranch: "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",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
claudeBranch: "claude/issue-789-20240101_120000",
|
||||
},
|
||||
};
|
||||
@@ -210,7 +210,7 @@ describe("generatePrompt", () => {
|
||||
eventAction: "assigned",
|
||||
isPR: false,
|
||||
issueNumber: "999",
|
||||
baseBranch: "develop",
|
||||
defaultBranch: "develop",
|
||||
claudeBranch: "claude/issue-999-20240101_120000",
|
||||
assigneeTrigger: "claude-bot",
|
||||
},
|
||||
@@ -238,7 +238,7 @@ describe("generatePrompt", () => {
|
||||
eventAction: "opened",
|
||||
isPR: false,
|
||||
issueNumber: "789",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
claudeBranch: "claude/issue-789-20240101_120000",
|
||||
},
|
||||
};
|
||||
@@ -285,7 +285,7 @@ describe("generatePrompt", () => {
|
||||
commentId: "67890",
|
||||
isPR: false,
|
||||
issueNumber: "123",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
claudeBranch: "claude/issue-67890-20240101_120000",
|
||||
commentBody: "@claude please fix this",
|
||||
},
|
||||
@@ -307,7 +307,7 @@ describe("generatePrompt", () => {
|
||||
commentId: "67890",
|
||||
isPR: false,
|
||||
issueNumber: "123",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
claudeBranch: "claude/issue-67890-20240101_120000",
|
||||
commentBody: "@claude please fix this",
|
||||
},
|
||||
@@ -362,7 +362,7 @@ describe("generatePrompt", () => {
|
||||
eventAction: "opened",
|
||||
isPR: false,
|
||||
issueNumber: "789",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
claudeBranch: "claude/issue-789-20240101_120000",
|
||||
},
|
||||
};
|
||||
@@ -400,7 +400,7 @@ describe("generatePrompt", () => {
|
||||
commentId: "67890",
|
||||
isPR: false,
|
||||
issueNumber: "123",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "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",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -470,7 +470,7 @@ describe("generatePrompt", () => {
|
||||
isPR: true,
|
||||
prNumber: "456",
|
||||
commentBody: "@claude please fix this",
|
||||
// No claudeBranch or baseBranch for open PRs
|
||||
// No claudeBranch or defaultBranch for open PRs
|
||||
},
|
||||
};
|
||||
|
||||
@@ -503,7 +503,7 @@ describe("generatePrompt", () => {
|
||||
prNumber: "789",
|
||||
commentBody: "@claude please update this",
|
||||
claudeBranch: "claude/pr-789-20240101_123000",
|
||||
baseBranch: "develop",
|
||||
defaultBranch: "develop",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -531,7 +531,7 @@ describe("generatePrompt", () => {
|
||||
commentId: "review-comment-123",
|
||||
commentBody: "@claude fix this issue",
|
||||
claudeBranch: "claude/pr-999-20240101_140000",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -559,7 +559,7 @@ describe("generatePrompt", () => {
|
||||
isPR: true,
|
||||
prNumber: "555",
|
||||
claudeBranch: "claude/pr-555-20240101_150000",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -604,7 +604,7 @@ describe("getEventTypeAndContext", () => {
|
||||
eventAction: "assigned",
|
||||
isPR: false,
|
||||
issueNumber: "999",
|
||||
baseBranch: "main",
|
||||
defaultBranch: "main",
|
||||
claudeBranch: "claude/issue-999-20240101_120000",
|
||||
assigneeTrigger: "claude-bot",
|
||||
},
|
||||
@@ -722,51 +722,4 @@ describe("buildDisallowedToolsString", () => {
|
||||
expect(parts).toContain("BadTool1");
|
||||
expect(parts).toContain("BadTool2");
|
||||
});
|
||||
|
||||
test("should remove hardcoded disallowed tools if they are in allowed tools", () => {
|
||||
const customDisallowedTools = "BadTool1,BadTool2";
|
||||
const allowedTools = "WebSearch,SomeOtherTool";
|
||||
const result = buildDisallowedToolsString(
|
||||
customDisallowedTools,
|
||||
allowedTools,
|
||||
);
|
||||
|
||||
// WebSearch should be removed from disallowed since it's in allowed
|
||||
expect(result).not.toContain("WebSearch");
|
||||
|
||||
// WebFetch should still be disallowed since it's not in allowed
|
||||
expect(result).toContain("WebFetch");
|
||||
|
||||
// Custom disallowed tools should still be present
|
||||
expect(result).toContain("BadTool1");
|
||||
expect(result).toContain("BadTool2");
|
||||
});
|
||||
|
||||
test("should remove all hardcoded disallowed tools if they are all in allowed tools", () => {
|
||||
const allowedTools = "WebSearch,WebFetch,SomeOtherTool";
|
||||
const result = buildDisallowedToolsString(undefined, allowedTools);
|
||||
|
||||
// Both hardcoded disallowed tools should be removed
|
||||
expect(result).not.toContain("WebSearch");
|
||||
expect(result).not.toContain("WebFetch");
|
||||
|
||||
// Result should be empty since no custom disallowed tools provided
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
test("should handle custom disallowed tools when all hardcoded tools are overridden", () => {
|
||||
const customDisallowedTools = "BadTool1,BadTool2";
|
||||
const allowedTools = "WebSearch,WebFetch";
|
||||
const result = buildDisallowedToolsString(
|
||||
customDisallowedTools,
|
||||
allowedTools,
|
||||
);
|
||||
|
||||
// Hardcoded tools should be removed
|
||||
expect(result).not.toContain("WebSearch");
|
||||
expect(result).not.toContain("WebFetch");
|
||||
|
||||
// Only custom disallowed tools should remain
|
||||
expect(result).toBe("BadTool1,BadTool2");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
beforeEach(() => {
|
||||
process.env = {
|
||||
...BASE_ENV,
|
||||
BASE_BRANCH: "main",
|
||||
DEFAULT_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.baseBranch).toBe("main");
|
||||
expect(result.eventData.defaultBranch).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 BASE_BRANCH is missing", () => {
|
||||
test("should throw error when DEFAULT_BRANCH is missing", () => {
|
||||
expect(() =>
|
||||
prepareContext(
|
||||
mockIssueCommentContext,
|
||||
@@ -83,7 +83,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
undefined,
|
||||
"claude/issue-67890-20240101_120000",
|
||||
),
|
||||
).toThrow("BASE_BRANCH is required for issue_comment event");
|
||||
).toThrow("DEFAULT_BRANCH is required for issue_comment event");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -151,7 +151,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
beforeEach(() => {
|
||||
process.env = {
|
||||
...BASE_ENV,
|
||||
BASE_BRANCH: "main",
|
||||
DEFAULT_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.baseBranch).toBe("main");
|
||||
expect(result.eventData.defaultBranch).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.baseBranch).toBe("main");
|
||||
expect(result.eventData.defaultBranch).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 BASE_BRANCH is missing for issues", () => {
|
||||
test("should throw error when DEFAULT_BRANCH is missing for issues", () => {
|
||||
expect(() =>
|
||||
prepareContext(
|
||||
mockIssueOpenedContext,
|
||||
@@ -217,7 +217,7 @@ describe("parseEnvVarsWithContext", () => {
|
||||
undefined,
|
||||
"claude/issue-42-20240101_120000",
|
||||
),
|
||||
).toThrow("BASE_BRANCH is required for issues event");
|
||||
).toThrow("DEFAULT_BRANCH is required for issues event");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user