Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions src/mcp/github-file-ops-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { resolve } from "path";
import { constants } from "fs";
import fetch from "node-fetch";
import { GITHUB_API_URL } from "../github/api/config";
import { retryWithBackoff } from "../utils/retry";
import { retryWithBackoff, NonRetryableError } from "../utils/retry";
import { validatePathWithinRepo } from "./path-validation";

type GitHubRef = {
Expand Down Expand Up @@ -399,14 +399,11 @@ server.tool(
throw permissionError;
}

// For other errors, use the original message
// For non-403 errors, fail immediately without retry
const error = new Error(
`Failed to update reference: ${updateRefResponse.status} - ${errorText}`,
);

// For non-403 errors, fail immediately without retry
console.error("Non-retryable error:", updateRefResponse.status);
throw error;
throw new NonRetryableError(error.message, error);
}
},
{
Expand Down Expand Up @@ -615,14 +612,11 @@ server.tool(
throw permissionError;
}

// For other errors, use the original message
// For non-403 errors, fail immediately without retry
const error = new Error(
`Failed to update reference: ${updateRefResponse.status} - ${errorText}`,
);

// For non-403 errors, fail immediately without retry
console.error("Non-retryable error:", updateRefResponse.status);
throw error;
throw new NonRetryableError(error.message, error);
}
},
{
Expand Down
20 changes: 20 additions & 0 deletions src/utils/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ export type RetryOptions = {
backoffFactor?: number;
};

/**
* Error class for errors that should not be retried.
* When thrown inside a retryWithBackoff operation, the retry loop
* will immediately rethrow without further attempts.
*/
export class NonRetryableError extends Error {
constructor(
message: string,
public readonly cause?: Error,
) {
super(message);
this.name = "NonRetryableError";
}
}

export async function retryWithBackoff<T>(
operation: () => Promise<T>,
options: RetryOptions = {},
Expand All @@ -24,6 +39,11 @@ export async function retryWithBackoff<T>(
console.log(`Attempt ${attempt} of ${maxAttempts}...`);
return await operation();
} catch (error) {
// Non-retryable errors should fail immediately without further retries
if (error instanceof NonRetryableError) {
throw error.cause ?? error;
}

lastError = error instanceof Error ? error : new Error(String(error));
console.error(`Attempt ${attempt} failed:`, lastError.message);

Expand Down