mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
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>
This commit is contained in:
@@ -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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user