Compare commits

..

1 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
4 changed files with 118 additions and 22 deletions

View File

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

View File

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

View File

@@ -145,6 +145,14 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
console.log(`Prompt file size: ${promptSize} bytes`); console.log(`Prompt file size: ${promptSize} bytes`);
// Log custom environment variables if any
const customEnvKeys = Object.keys(config.env).filter(
(key) => key !== "CLAUDE_ACTION_INPUTS_PRESENT",
);
if (customEnvKeys.length > 0) {
console.log(`Custom environment variables: ${customEnvKeys.join(", ")}`);
}
// Output to console // Output to console
console.log(`Running Claude with prompt from file: ${config.promptPath}`); console.log(`Running Claude with prompt from file: ${config.promptPath}`);
@@ -160,11 +168,6 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
pipeStream.destroy(); pipeStream.destroy();
}); });
console.log("yolo", process.env);
console.log("yolo running with", {
...process.env,
...config.env,
});
const claudeProcess = spawn("claude", config.claudeArgs, { const claudeProcess = spawn("claude", config.claudeArgs, {
stdio: ["pipe", "pipe", "inherit"], stdio: ["pipe", "pipe", "inherit"],
env: { env: {
@@ -304,10 +307,7 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
await writeFile("output.txt", output); await writeFile("output.txt", output);
// Process output.txt into JSON and save to execution file // Process output.txt into JSON and save to execution file
// Increase maxBuffer from Node.js default of 1MB to 10MB to handle large Claude outputs const { stdout: jsonOutput } = await execAsync("jq -s '.' output.txt");
const { stdout: jsonOutput } = await execAsync("jq -s '.' output.txt", {
maxBuffer: 10 * 1024 * 1024,
});
await writeFile(EXECUTION_FILE, jsonOutput); await writeFile(EXECUTION_FILE, jsonOutput);
console.log(`Log saved to ${EXECUTION_FILE}`); console.log(`Log saved to ${EXECUTION_FILE}`);
@@ -324,10 +324,7 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
if (output) { if (output) {
try { try {
await writeFile("output.txt", output); await writeFile("output.txt", output);
// Increase maxBuffer from Node.js default of 1MB to 10MB to handle large Claude outputs const { stdout: jsonOutput } = await execAsync("jq -s '.' output.txt");
const { stdout: jsonOutput } = await execAsync("jq -s '.' output.txt", {
maxBuffer: 10 * 1024 * 1024,
});
await writeFile(EXECUTION_FILE, jsonOutput); await writeFile(EXECUTION_FILE, jsonOutput);
core.setOutput("execution_file", EXECUTION_FILE); core.setOutput("execution_file", EXECUTION_FILE);
} catch (e) { } catch (e) {

View File

@@ -367,6 +367,7 @@ server.tool(
// We're seeing intermittent 403 "Resource not accessible by integration" errors // We're seeing intermittent 403 "Resource not accessible by integration" errors
// on certain repos when updating git references. These appear to be transient // on certain repos when updating git references. These appear to be transient
// GitHub API issues that succeed on retry. // GitHub API issues that succeed on retry.
let lastErrorDetails: any = null;
await retryWithBackoff( await retryWithBackoff(
async () => { async () => {
const updateRefResponse = await fetch(updateRefUrl, { const updateRefResponse = await fetch(updateRefUrl, {
@@ -385,17 +386,48 @@ server.tool(
if (!updateRefResponse.ok) { if (!updateRefResponse.ok) {
const errorText = await updateRefResponse.text(); 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( 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 // Only retry on 403 errors - these are the intermittent failures we're targeting
if (updateRefResponse.status === 403) { if (updateRefResponse.status === 403) {
console.error("403 Error encountered (will retry):", debugInfo);
throw error; throw error;
} }
// For non-403 errors, fail immediately without retry // For non-403 errors, fail immediately without retry
console.error("Non-retryable error:", updateRefResponse.status); console.error("Non-retryable error:", debugInfo);
throw error; throw error;
} }
}, },
@@ -405,7 +437,23 @@ server.tool(
maxDelayMs: 5000, // Max 5 seconds delay maxDelayMs: 5000, // Max 5 seconds delay
backoffFactor: 2, // Double the delay each time 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 = { const simplifiedResult = {
commit: { commit: {
@@ -573,6 +621,7 @@ server.tool(
// We're seeing intermittent 403 "Resource not accessible by integration" errors // We're seeing intermittent 403 "Resource not accessible by integration" errors
// on certain repos when updating git references. These appear to be transient // on certain repos when updating git references. These appear to be transient
// GitHub API issues that succeed on retry. // GitHub API issues that succeed on retry.
let lastErrorDetails: any = null;
await retryWithBackoff( await retryWithBackoff(
async () => { async () => {
const updateRefResponse = await fetch(updateRefUrl, { const updateRefResponse = await fetch(updateRefUrl, {
@@ -591,18 +640,52 @@ server.tool(
if (!updateRefResponse.ok) { if (!updateRefResponse.ok) {
const errorText = await updateRefResponse.text(); 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( 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 // Only retry on 403 errors - these are the intermittent failures we're targeting
if (updateRefResponse.status === 403) { if (updateRefResponse.status === 403) {
console.log("Received 403 error, will retry..."); console.error(
"403 Error encountered during delete (will retry):",
debugInfo,
);
throw error; throw error;
} }
// For non-403 errors, fail immediately without retry // 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; throw error;
} }
}, },
@@ -612,7 +695,23 @@ server.tool(
maxDelayMs: 5000, // Max 5 seconds delay maxDelayMs: 5000, // Max 5 seconds delay
backoffFactor: 2, // Double the delay each time 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 = { const simplifiedResult = {
commit: { commit: {