Compare commits

..

5 Commits

Author SHA1 Message Date
Lina Tawfik
cc172c74aa feat: add comprehensive response logging for GitHub API errors
- Log entire raw response body in text, hex, and base64 formats
- Capture complete request context including timing and memory usage
- Add detailed error response parsing with fallback handling
- Include environment and operation state context
- Special handling for 500 errors with GitHub request IDs
- Log all response headers including rate limit info
2025-05-23 17:09:55 -07:00
Lina Tawfik
cb9aae1881 feat: enhance error logging in MCP server for better diagnostics
- Add detailed error logging helper function
- Enhance error handling in commit_files and delete_files
- Capture GitHub request IDs for 500 errors
- Improve error message construction to avoid 'undefined'
- Add comprehensive logging for fetch errors and API responses
2025-05-23 17:05:16 -07:00
Lina Tawfik
b02a95b3f3 Remove token-related logging for security
- Remove token type detection logging
- Remove token age/creation time logging
- Keep only non-sensitive debugging info (repo, branch, request IDs)

While the token value was never logged, it's better to err on the side
of caution and not log any token-related information.
2025-05-23 16:20:31 -07:00
Lina Tawfik
cde954dda0 Add comprehensive logging for debugging 500 errors in commit_files
- Log token type (GitHub App vs PAT) and age to detect expiration issues
- Add detailed logging at ref update step where 500 error occurs
- Capture GitHub Request ID for support tickets
- Log environment context (repo, branch, files)
- Fix error handling to throw errors instead of returning custom objects
- Pass TOKEN_CREATED_AT and GITHUB_API_URL to MCP subprocess

This will help diagnose why commit_files gets 500 errors on ref updates
while tree/commit creation succeeds.
2025-05-23 15:52:39 -07:00
Lina Tawfik
c845eee45f Use local action for testing in same repository
When testing an action within its own repository, use './' instead of
the published action reference.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-23 14:17:02 -07:00
16 changed files with 943 additions and 217 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

@@ -31,6 +31,6 @@ jobs:
- name: Run Claude Code - name: Run Claude Code
id: claude id: claude
uses: anthropics/claude-code-action@beta uses: ./
with: with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

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 node_modules
**/.claude/settings.local.json **/.claude/settings.local.json

View File

@@ -446,7 +446,7 @@ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
``` ```
This applies to all sensitive values including API keys, access tokens, and credentials. 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 ## License

View File

@@ -67,7 +67,7 @@ runs:
using: "composite" using: "composite"
steps: steps:
- name: Install Bun - 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: with:
bun-version: 1.2.11 bun-version: 1.2.11
@@ -94,7 +94,7 @@ runs:
- name: Run Claude Code - name: Run Claude Code
id: claude-code id: claude-code
if: steps.prepare.outputs.contains_trigger == 'true' if: steps.prepare.outputs.contains_trigger == 'true'
uses: anthropics/claude-code-base-action@266585c92dd90d61d3806a3367582c4f6224e892 # https://github.com/anthropics/claude-code-base-action/releases/tag/v0.0.6 uses: anthropics/claude-code-base-action@beta
with: with:
prompt_file: /tmp/claude-prompts/claude-prompt.txt prompt_file: /tmp/claude-prompts/claude-prompt.txt
allowed_tools: ${{ env.ALLOWED_TOOLS }} allowed_tools: ${{ env.ALLOWED_TOOLS }}

93
check-pr-status.ts Normal file
View 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
View 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
View 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

Binary file not shown.

View File

@@ -51,9 +51,8 @@ export async function setupBranch(
const branchName = prData.headRefName; const branchName = prData.headRefName;
// Execute git commands to checkout PR branch (shallow fetch for performance) // Execute git commands to checkout PR branch
// Fetch the branch with a depth of 20 to avoid fetching too much history, while still allowing for some context await $`git fetch origin ${branchName}`;
await $`git fetch origin --depth=20 ${branchName}`;
await $`git checkout ${branchName}`; await $`git checkout ${branchName}`;
console.log(`Successfully checked out PR branch for PR #${entityNumber}`); console.log(`Successfully checked out PR branch for PR #${entityNumber}`);
@@ -99,8 +98,8 @@ export async function setupBranch(
sha: currentSHA, sha: currentSHA,
}); });
// Checkout the new branch (shallow fetch for performance) // Checkout the new branch
await $`git fetch origin --depth=1 ${newBranch}`; await $`git fetch origin ${newBranch}`;
await $`git checkout ${newBranch}`; await $`git checkout ${newBranch}`;
console.log( console.log(

View File

@@ -39,19 +39,25 @@ async function retryWithBackoff<T>(
} }
} }
console.error(`Operation failed after ${maxAttempts} attempts`); throw new Error(
throw lastError; `Operation failed after ${maxAttempts} attempts. Last error: ${
lastError?.message ?? "Unknown error"
}`,
);
} }
async function getOidcToken(): Promise<string> { async function getOidcToken(): Promise<string> {
try { try {
const oidcToken = await core.getIDToken("claude-code-github-action"); const oidcToken = await core.getIDToken("claude-code-github-action");
if (!oidcToken) {
throw new Error("OIDC token not found");
}
return oidcToken; return oidcToken;
} catch (error) { } catch (error) {
console.error("Failed to get OIDC token:", error);
throw new 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) { if (!response.ok) {
const responseJson = (await response.json()) as { throw new Error(
error?: { `App token exchange failed: ${response.status} ${response.statusText}`,
message?: string;
};
};
console.error(
`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson?.error?.message ?? "Unknown error"}`,
); );
throw new Error(`${responseJson?.error?.message ?? "Unknown error"}`);
} }
const appTokenData = (await response.json()) as { const appTokenData = (await response.json()) as {
@@ -117,9 +117,7 @@ export async function setupGitHubToken(): Promise<string> {
core.setOutput("GITHUB_TOKEN", appToken); core.setOutput("GITHUB_TOKEN", appToken);
return appToken; return appToken;
} catch (error) { } catch (error) {
core.setFailed( core.setFailed(`Failed to setup GitHub token: ${error}`);
`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.`,
);
process.exit(1); process.exit(1);
} }
} }

View File

@@ -1,12 +1,13 @@
#!/usr/bin/env node #!/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 { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod"; import { z } from "zod";
import { readFile } from "fs/promises"; import { readFile } from "fs/promises";
import { join } from "path"; import { join } from "path";
import fetch from "node-fetch"; 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 = { type GitHubRef = {
object: { object: {
@@ -51,6 +52,18 @@ const server = new McpServer({
version: "0.0.1", 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 // Commit files tool
server.tool( server.tool(
"commit_files", "commit_files",
@@ -67,6 +80,19 @@ server.tool(
const owner = REPO_OWNER; const owner = REPO_OWNER;
const repo = REPO_NAME; const repo = REPO_NAME;
const branch = BRANCH_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 { try {
const githubToken = process.env.GITHUB_TOKEN; const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) { if (!githubToken) {
@@ -123,7 +149,12 @@ server.tool(
? filePath ? filePath
: join(REPO_DIR, 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 { return {
path: filePath, path: filePath,
mode: "100644", mode: "100644",
@@ -186,24 +217,158 @@ server.tool(
// 6. Update the reference to point to the new commit // 6. Update the reference to point to the new commit
const updateRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${branch}`; const updateRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${branch}`;
const updateRefResponse = await fetch(updateRefUrl, { console.error(`[commit_files] Updating reference: ${updateRefUrl}`);
method: "PATCH", 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: { headers: {
Accept: "application/vnd.github+json", ...requestHeaders,
Authorization: `Bearer ${githubToken}`, Authorization: `Bearer [TOKEN_LENGTH:${githubToken?.length || 0}]`,
"X-GitHub-Api-Version": "2022-11-28",
"Content-Type": "application/json",
}, },
body: JSON.stringify({ body: requestBody,
sha: newCommitData.sha, timestamp: new Date().toISOString(),
force: false, 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) { 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( 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) { } catch (error) {
logDetailedError('commit_files', error);
const errorMessage = const errorMessage =
error instanceof Error ? error.message : String(error); error instanceof Error ? error.message : String(error);
return { console.error(`[commit_files] Final error message being thrown: "${errorMessage}"`);
content: [
{ // Ensure we're throwing a proper Error object with a message
type: "text", if (!errorMessage || errorMessage === 'undefined' || errorMessage === '[object Object]') {
text: `Error: ${errorMessage}`, 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]}`
error: errorMessage, : 'Failed to commit files: Unknown error occurred';
isError: true, throw new Error(fallbackMessage);
}; }
throw new Error(errorMessage);
} }
}, },
); );
@@ -380,22 +548,71 @@ server.tool(
// 6. Update the reference to point to the new commit // 6. Update the reference to point to the new commit
const updateRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${branch}`; const updateRefUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs/heads/${branch}`;
const updateRefResponse = await fetch(updateRefUrl, { console.error(`[delete_files] Updating reference: ${updateRefUrl}`);
method: "PATCH", console.error(`[delete_files] New commit SHA: ${newCommitData.sha}`);
headers: { console.error(`[delete_files] Base SHA was: ${baseSha}`);
Accept: "application/vnd.github+json", console.error(`[delete_files] Request body:`, JSON.stringify({
Authorization: `Bearer ${githubToken}`, sha: newCommitData.sha,
"X-GitHub-Api-Version": "2022-11-28", force: false,
"Content-Type": "application/json", }));
},
body: JSON.stringify({ let updateRefResponse;
sha: newCommitData.sha, try {
force: false, 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) { 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( throw new Error(
`Failed to update reference: ${updateRefResponse.status} - ${errorText}`, `Failed to update reference: ${updateRefResponse.status} - ${errorText}`,
); );
@@ -423,18 +640,21 @@ server.tool(
], ],
}; };
} catch (error) { } catch (error) {
logDetailedError('delete_files', error);
const errorMessage = const errorMessage =
error instanceof Error ? error.message : String(error); error instanceof Error ? error.message : String(error);
return { console.error(`[delete_files] Final error message being thrown: "${errorMessage}"`);
content: [
{ // Ensure we're throwing a proper Error object with a message
type: "text", if (!errorMessage || errorMessage === 'undefined' || errorMessage === '[object Object]') {
text: `Error: ${errorMessage}`, 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]}`
error: errorMessage, : 'Failed to delete files: Unknown error occurred';
isError: true, throw new Error(fallbackMessage);
}; }
throw new Error(errorMessage);
} }
}, },
); );

View File

@@ -35,6 +35,7 @@ export async function prepareMcpConfig(
REPO_NAME: repo, REPO_NAME: repo,
BRANCH_NAME: branch, BRANCH_NAME: branch,
REPO_DIR: process.env.GITHUB_WORKSPACE || process.cwd(), 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
View 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);