Compare commits

..

3 Commits

Author SHA1 Message Date
Ashwin Bhat
6147037db9 feat: add detailed debugging info for intermittent 403 errors on ref updates
Enhanced error messages when GitHub API returns "Resource not accessible by integration"
errors during git reference updates. The improved error output includes:
- Repository and branch context
- Token metadata (length and prefix)
- Parent and target SHA details
- GitHub Actions environment info
- Full API response details
- Debugging hints for common causes

This helps diagnose whether failures are due to protected branches, permission
issues, race conditions, or transient GitHub API issues.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-19 20:52:32 -07:00
Chris Lloyd
194fca8b05 feat: preserve file permissions when committing via GitHub API (#469)
Add file permission detection to github-file-ops-server.ts to properly preserve file modes (regular, executable, symlink) when committing files through the GitHub API. This ensures executable scripts and other special files maintain their correct permissions.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-19 17:18:05 -07:00
GitHub Actions
0f913a6e0e chore: bump Claude Code version to 1.0.85 2025-08-19 23:59:52 +00:00
3 changed files with 143 additions and 12 deletions

View File

@@ -178,7 +178,7 @@ runs:
echo "Base-action dependencies installed"
cd -
# Install Claude Code globally
curl -fsSL https://claude.ai/install.sh | bash -s 1.0.84
curl -fsSL https://claude.ai/install.sh | bash -s 1.0.85
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Setup Network Restrictions

View File

@@ -118,7 +118,7 @@ runs:
- name: Install Claude Code
shell: bash
run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.84
run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.85
- name: Run Claude Code Action
shell: bash

View File

@@ -3,8 +3,9 @@
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 { readFile, stat } from "fs/promises";
import { join } from "path";
import { constants } from "fs";
import fetch from "node-fetch";
import { GITHUB_API_URL } from "../github/api/config";
import { retryWithBackoff } from "../utils/retry";
@@ -162,6 +163,34 @@ async function getOrCreateBranchRef(
return baseSha;
}
// Get the appropriate Git file mode for a file
async function getFileMode(filePath: string): Promise<string> {
try {
const fileStat = await stat(filePath);
if (fileStat.isFile()) {
// Check if execute bit is set for user
if (fileStat.mode & constants.S_IXUSR) {
return "100755"; // Executable file
} else {
return "100644"; // Regular file
}
} else if (fileStat.isDirectory()) {
return "040000"; // Directory (tree)
} else if (fileStat.isSymbolicLink()) {
return "120000"; // Symbolic link
} else {
// Fallback for unknown file types
return "100644";
}
} catch (error) {
// If we can't stat the file, default to regular file
console.warn(
`Could not determine file mode for ${filePath}, using default: ${error}`,
);
return "100644";
}
}
// Commit files tool
server.tool(
"commit_files",
@@ -223,6 +252,9 @@ server.tool(
? filePath
: join(REPO_DIR, filePath);
// Get the proper file mode based on file permissions
const fileMode = await getFileMode(fullPath);
// Check if file is binary (images, etc.)
const isBinaryFile =
/\.(png|jpg|jpeg|gif|webp|ico|pdf|zip|tar|gz|exe|bin|woff|woff2|ttf|eot)$/i.test(
@@ -261,7 +293,7 @@ server.tool(
// Return tree entry with blob SHA
return {
path: filePath,
mode: "100644",
mode: fileMode,
type: "blob",
sha: blobData.sha,
};
@@ -270,7 +302,7 @@ server.tool(
const content = await readFile(fullPath, "utf-8");
return {
path: filePath,
mode: "100644",
mode: fileMode,
type: "blob",
content: content,
};
@@ -335,6 +367,7 @@ server.tool(
// We're seeing intermittent 403 "Resource not accessible by integration" errors
// on certain repos when updating git references. These appear to be transient
// GitHub API issues that succeed on retry.
let lastErrorDetails: any = null;
await retryWithBackoff(
async () => {
const updateRefResponse = await fetch(updateRefUrl, {
@@ -353,17 +386,48 @@ server.tool(
if (!updateRefResponse.ok) {
const errorText = await updateRefResponse.text();
let errorJson: any = {};
try {
errorJson = JSON.parse(errorText);
} catch {
// If not JSON, use the text as-is
}
// Collect debugging information
const debugInfo = {
status: updateRefResponse.status,
statusText: updateRefResponse.statusText,
headers: Object.fromEntries(updateRefResponse.headers.entries()),
errorBody: errorJson || errorText,
context: {
repository: `${owner}/${repo}`,
branch: branch,
baseBranch: process.env.BASE_BRANCH,
targetSha: newCommitData.sha,
parentSha: baseSha,
isGitHubAction: !!process.env.GITHUB_ACTIONS,
eventName: process.env.GITHUB_EVENT_NAME,
isPR: process.env.IS_PR,
tokenLength: githubToken?.length || 0,
tokenPrefix: githubToken?.substring(0, 10) + "...",
apiUrl: updateRefUrl,
},
};
lastErrorDetails = debugInfo;
const error = new Error(
`Failed to update reference: ${updateRefResponse.status} - ${errorText}`,
`Failed to update reference: ${updateRefResponse.status} - ${errorText}\n\nDebug Info: ${JSON.stringify(debugInfo, null, 2)}`,
);
// Only retry on 403 errors - these are the intermittent failures we're targeting
if (updateRefResponse.status === 403) {
console.error("403 Error encountered (will retry):", debugInfo);
throw error;
}
// For non-403 errors, fail immediately without retry
console.error("Non-retryable error:", updateRefResponse.status);
console.error("Non-retryable error:", debugInfo);
throw error;
}
},
@@ -373,7 +437,23 @@ server.tool(
maxDelayMs: 5000, // Max 5 seconds delay
backoffFactor: 2, // Double the delay each time
},
).catch((error) => {
// If all retries failed, enhance the error message with collected details
if (lastErrorDetails) {
throw new Error(
`All retry attempts failed for ref update.\n\n` +
`Final error: ${error.message}\n\n` +
`Debugging hints:\n` +
`- Check if branch '${branch}' is protected and the GitHub App has bypass permissions\n` +
`- Verify the token has 'contents:write' permission for ${owner}/${repo}\n` +
`- Check for concurrent operations updating the same branch\n` +
`- Token appears to be: ${lastErrorDetails.context.tokenPrefix}\n` +
`- This may be a transient GitHub API issue if it works on retry\n\n` +
`Full debug details: ${JSON.stringify(lastErrorDetails, null, 2)}`,
);
}
throw error;
});
const simplifiedResult = {
commit: {
@@ -541,6 +621,7 @@ server.tool(
// We're seeing intermittent 403 "Resource not accessible by integration" errors
// on certain repos when updating git references. These appear to be transient
// GitHub API issues that succeed on retry.
let lastErrorDetails: any = null;
await retryWithBackoff(
async () => {
const updateRefResponse = await fetch(updateRefUrl, {
@@ -559,18 +640,52 @@ server.tool(
if (!updateRefResponse.ok) {
const errorText = await updateRefResponse.text();
let errorJson: any = {};
try {
errorJson = JSON.parse(errorText);
} catch {
// If not JSON, use the text as-is
}
// Collect debugging information
const debugInfo = {
status: updateRefResponse.status,
statusText: updateRefResponse.statusText,
headers: Object.fromEntries(updateRefResponse.headers.entries()),
errorBody: errorJson || errorText,
context: {
operation: "delete_files",
repository: `${owner}/${repo}`,
branch: branch,
baseBranch: process.env.BASE_BRANCH,
targetSha: newCommitData.sha,
parentSha: baseSha,
isGitHubAction: !!process.env.GITHUB_ACTIONS,
eventName: process.env.GITHUB_EVENT_NAME,
isPR: process.env.IS_PR,
tokenLength: githubToken?.length || 0,
tokenPrefix: githubToken?.substring(0, 10) + "...",
apiUrl: updateRefUrl,
},
};
lastErrorDetails = debugInfo;
const error = new Error(
`Failed to update reference: ${updateRefResponse.status} - ${errorText}`,
`Failed to update reference: ${updateRefResponse.status} - ${errorText}\n\nDebug Info: ${JSON.stringify(debugInfo, null, 2)}`,
);
// Only retry on 403 errors - these are the intermittent failures we're targeting
if (updateRefResponse.status === 403) {
console.log("Received 403 error, will retry...");
console.error(
"403 Error encountered during delete (will retry):",
debugInfo,
);
throw error;
}
// For non-403 errors, fail immediately without retry
console.error("Non-retryable error:", updateRefResponse.status);
console.error("Non-retryable error during delete:", debugInfo);
throw error;
}
},
@@ -580,7 +695,23 @@ server.tool(
maxDelayMs: 5000, // Max 5 seconds delay
backoffFactor: 2, // Double the delay each time
},
).catch((error) => {
// If all retries failed, enhance the error message with collected details
if (lastErrorDetails) {
throw new Error(
`All retry attempts failed for ref update during file deletion.\n\n` +
`Final error: ${error.message}\n\n` +
`Debugging hints:\n` +
`- Check if branch '${branch}' is protected and the GitHub App has bypass permissions\n` +
`- Verify the token has 'contents:write' permission for ${owner}/${repo}\n` +
`- Check for concurrent operations updating the same branch\n` +
`- Token appears to be: ${lastErrorDetails.context.tokenPrefix}\n` +
`- This may be a transient GitHub API issue if it works on retry\n\n` +
`Full debug details: ${JSON.stringify(lastErrorDetails, null, 2)}`,
);
}
throw error;
});
const simplifiedResult = {
commit: {