diff --git a/check-pr-status.ts b/check-pr-status.ts new file mode 100644 index 0000000..5ea6244 --- /dev/null +++ b/check-pr-status.ts @@ -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 "); + process.exit(1); +} + +checkPRStatus(token); \ No newline at end of file diff --git a/diagnose-app-permissions.ts b/diagnose-app-permissions.ts new file mode 100644 index 0000000..27b6a27 --- /dev/null +++ b/diagnose-app-permissions.ts @@ -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 "); + console.log("\nThis script helps diagnose why GitHub App permissions are inconsistent."); + process.exit(1); +} + +diagnosePermissions(token, owner, repo); \ No newline at end of file diff --git a/revert-test-commit.ts b/revert-test-commit.ts new file mode 100644 index 0000000..51cdf6c --- /dev/null +++ b/revert-test-commit.ts @@ -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 "); + process.exit(1); +} + +revertTestCommit(token); \ No newline at end of file diff --git a/src/mcp/github-file-ops-server.ts b/src/mcp/github-file-ops-server.ts index 5495516..f9d97a6 100644 --- a/src/mcp/github-file-ops-server.ts +++ b/src/mcp/github-file-ops-server.ts @@ -220,70 +220,155 @@ server.tool( console.error(`[commit_files] Updating reference: ${updateRefUrl}`); console.error(`[commit_files] New commit SHA: ${newCommitData.sha}`); console.error(`[commit_files] Base SHA was: ${baseSha}`); - console.error(`[commit_files] Request body:`, JSON.stringify({ + + // 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: { - 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, - }), + headers: requestHeaders, + body: requestBody, }); } catch (fetchError) { - console.error(`[commit_files] FETCH ERROR during reference update:`, 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: ${fetchError?.message || 'Unknown fetch error'}`); + 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) { - let errorText; + 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 { - errorText = await updateRefResponse.text(); - } catch (textError) { - console.error(`[commit_files] Failed to read error response text:`, textError); - errorText = 'Unable to read error response'; + // 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}`; } - console.error(`[commit_files] Update reference error body: "${errorText}"`); - console.error(`[commit_files] Error body length: ${errorText?.length}`); - console.error(`[commit_files] Error body type: ${typeof errorText}`); + // 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`, + }); - // Log additional debugging info for 500 errors + // 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 appears to be an internal GitHub error`); - console.error(`[commit_files] Token was valid for tree/commit creation but failed for ref update`); - console.error(`[commit_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(`[commit_files] Parsed error:`, parsedError); - } - } catch (e) { - console.error(`[commit_files] Error text is not JSON`); + 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'}`, ); } diff --git a/test-with-new-branch.ts b/test-with-new-branch.ts new file mode 100644 index 0000000..0ae1f8f --- /dev/null +++ b/test-with-new-branch.ts @@ -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 + +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 "); + 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); \ No newline at end of file