From ca9c340df6c34328369bf44bd87d5f89713dded1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 30 May 2026 18:21:39 +0000 Subject: [PATCH 1/7] Initial plan From a533b4b46fb6f84860a06844ef0d3b7b1372ffee Mon Sep 17 00:00:00 2001 From: Naheel Muhammed Date: Sat, 30 May 2026 23:28:10 +0530 Subject: [PATCH 2/7] fix: clarify missing repository errors --- src/lib/octokit.ts | 53 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/lib/octokit.ts b/src/lib/octokit.ts index 91c00f4..9a09d50 100644 --- a/src/lib/octokit.ts +++ b/src/lib/octokit.ts @@ -4,6 +4,7 @@ type RepoAccessErrorCode = | "AUTH_REQUIRED" | "NOT_FOUND" | "FORBIDDEN" + | "RATE_LIMITED" | "UNKNOWN"; export class RepoAccessError extends Error { @@ -40,6 +41,56 @@ function toRepoAccessError( ? error.status : 500; + const responseHeaders = + typeof error === "object" && + error !== null && + "response" in error && + typeof error.response === "object" && + error.response !== null && + "headers" in error.response && + typeof error.response.headers === "object" && + error.response.headers !== null + ? error.response.headers + : undefined; + + const responseMessage = + typeof error === "object" && + error !== null && + "response" in error && + typeof error.response === "object" && + error.response !== null && + "data" in error.response && + typeof error.response.data === "object" && + error.response.data !== null && + "message" in error.response.data && + typeof error.response.data.message === "string" + ? error.response.data.message + : ""; + + const rateLimitRemaining = + responseHeaders && + "x-ratelimit-remaining" in responseHeaders && + responseHeaders["x-ratelimit-remaining"]; + + const retryAfter = + responseHeaders && + "retry-after" in responseHeaders && + responseHeaders["retry-after"]; + + if ( + status === 429 || + (status === 403 && + (String(rateLimitRemaining) === "0" || + retryAfter !== undefined || + responseMessage.toLowerCase().includes("rate limit"))) + ) { + return new RepoAccessError( + "GitHub API rate limit reached. Please wait a few minutes and try again.", + 429, + "RATE_LIMITED", + ); + } + if (status === 401) { return new RepoAccessError( "Access token expired or invalid. Please re-authenticate with GitHub.", @@ -58,7 +109,7 @@ function toRepoAccessError( if (status === 404) { return new RepoAccessError( - "Repository not found or you do not have access to it.", + "Repository not found. Please check the URL and try again.", 404, "NOT_FOUND", ); From 8235ad1dbdf3a632dc9e65c199ba042873fe1168 Mon Sep 17 00:00:00 2001 From: Naheel Muhammed Date: Sat, 30 May 2026 23:45:51 +0530 Subject: [PATCH 3/7] fix(octokit): resolve default branch to tree SHA and improve error handling --- src/lib/octokit.ts | 57 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/lib/octokit.ts b/src/lib/octokit.ts index 9a09d50..0a3a269 100644 --- a/src/lib/octokit.ts +++ b/src/lib/octokit.ts @@ -50,7 +50,7 @@ function toRepoAccessError( "headers" in error.response && typeof error.response.headers === "object" && error.response.headers !== null - ? error.response.headers + ? (error.response.headers as Record) : undefined; const responseMessage = @@ -67,22 +67,24 @@ function toRepoAccessError( ? error.response.data.message : ""; - const rateLimitRemaining = - responseHeaders && - "x-ratelimit-remaining" in responseHeaders && - responseHeaders["x-ratelimit-remaining"]; + // Normalize header lookup to be case-insensitive + const getHeader = (name: string) => { + if (!responseHeaders) return undefined; + const found = Object.keys(responseHeaders).find( + (k) => k.toLowerCase() === name.toLowerCase(), + ); + return found ? responseHeaders[found] : undefined; + }; - const retryAfter = - responseHeaders && - "retry-after" in responseHeaders && - responseHeaders["retry-after"]; + const rateLimitRemaining = getHeader("x-ratelimit-remaining"); + const retryAfter = getHeader("retry-after"); if ( status === 429 || (status === 403 && (String(rateLimitRemaining) === "0" || retryAfter !== undefined || - responseMessage.toLowerCase().includes("rate limit"))) + (typeof responseMessage === "string" && responseMessage.toLowerCase().includes("rate limit")))) ) { return new RepoAccessError( "GitHub API rate limit reached. Please wait a few minutes and try again.", @@ -99,10 +101,10 @@ function toRepoAccessError( ); } - if ((status === 403 || status === 404) && !hasUserAccessToken) { + if (status === 403 && !hasUserAccessToken) { return new RepoAccessError( "Repository not accessible. It may be private or unavailable. Log in with GitHub if you need access to a private repository.", - 401, + 403, "AUTH_REQUIRED", ); } @@ -142,10 +144,39 @@ export async function getRepoSnapshot( repo, }); + // Resolve the branch to a tree SHA (default_branch is a name, not a SHA) + let treeSha: string | undefined = undefined; + try { + const { data: branch } = await client.rest.repos.getBranch({ + owner, + repo, + branch: repoInfo.default_branch, + }); + + const getNestedString = (obj: unknown, path: string[]): string | undefined => { + let cur: unknown = obj; + for (const p of path) { + if (typeof cur === "object" && cur !== null && p in (cur as Record)) { + cur = (cur as Record)[p]; + } else { + return undefined; + } + } + return typeof cur === "string" ? cur : undefined; + }; + + treeSha = + getNestedString(branch, ["commit", "commit", "tree", "sha"]) || + getNestedString(branch, ["commit", "sha"]); + } catch { + // If resolving the branch fails, fall back to the previous behavior + treeSha = undefined; + } + const { data: repoTree } = await client.rest.git.getTree({ owner, repo, - tree_sha: repoInfo.default_branch, + tree_sha: treeSha ?? repoInfo.default_branch, }); type RepoTreeItem = (typeof repoTree.tree)[number]; From 5626510865d4fdf0391f7d696a748561c0ef90aa Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 18:18:10 +0000 Subject: [PATCH 4/7] [autofix.ci] apply automated fixes --- src/lib/octokit.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/octokit.ts b/src/lib/octokit.ts index 0a3a269..dfb00ea 100644 --- a/src/lib/octokit.ts +++ b/src/lib/octokit.ts @@ -84,7 +84,8 @@ function toRepoAccessError( (status === 403 && (String(rateLimitRemaining) === "0" || retryAfter !== undefined || - (typeof responseMessage === "string" && responseMessage.toLowerCase().includes("rate limit")))) + (typeof responseMessage === "string" && + responseMessage.toLowerCase().includes("rate limit")))) ) { return new RepoAccessError( "GitHub API rate limit reached. Please wait a few minutes and try again.", @@ -153,10 +154,17 @@ export async function getRepoSnapshot( branch: repoInfo.default_branch, }); - const getNestedString = (obj: unknown, path: string[]): string | undefined => { + const getNestedString = ( + obj: unknown, + path: string[], + ): string | undefined => { let cur: unknown = obj; for (const p of path) { - if (typeof cur === "object" && cur !== null && p in (cur as Record)) { + if ( + typeof cur === "object" && + cur !== null && + p in (cur as Record) + ) { cur = (cur as Record)[p]; } else { return undefined; From f2ad6fb2c0619b3c7d7764c26441332af0cf0367 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 30 May 2026 18:26:27 +0000 Subject: [PATCH 5/7] fix: defer branch lookup and resolve tree sha from commit --- src/lib/octokit.ts | 91 ++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/src/lib/octokit.ts b/src/lib/octokit.ts index dfb00ea..93d941e 100644 --- a/src/lib/octokit.ts +++ b/src/lib/octokit.ts @@ -139,53 +139,72 @@ export async function getRepoSnapshot( ) { const client = createOctokit(accessToken); + const getNestedString = (obj: unknown, path: string[]): string | undefined => { + let cur: unknown = obj; + for (const p of path) { + if ( + typeof cur === "object" && + cur !== null && + p in (cur as Record) + ) { + cur = (cur as Record)[p]; + } else { + return undefined; + } + } + return typeof cur === "string" ? cur : undefined; + }; + try { const { data: repoInfo } = await client.rest.repos.get({ owner, repo, }); - - // Resolve the branch to a tree SHA (default_branch is a name, not a SHA) - let treeSha: string | undefined = undefined; + let repoTree: Awaited>["data"]; try { - const { data: branch } = await client.rest.repos.getBranch({ + ({ data: repoTree } = await client.rest.git.getTree({ owner, repo, - branch: repoInfo.default_branch, - }); - - const getNestedString = ( - obj: unknown, - path: string[], - ): string | undefined => { - let cur: unknown = obj; - for (const p of path) { - if ( - typeof cur === "object" && - cur !== null && - p in (cur as Record) - ) { - cur = (cur as Record)[p]; - } else { - return undefined; + tree_sha: repoInfo.default_branch, + })); + } catch (initialTreeError: unknown) { + let resolvedTreeSha: string | undefined = undefined; + + try { + const { data: branch } = await client.rest.repos.getBranch({ + owner, + repo, + branch: repoInfo.default_branch, + }); + + resolvedTreeSha = getNestedString(branch, ["commit", "commit", "tree", "sha"]); + + if (!resolvedTreeSha) { + const commitSha = getNestedString(branch, ["commit", "sha"]); + if (commitSha) { + const { data: commit } = await client.rest.git.getCommit({ + owner, + repo, + commit_sha: commitSha, + }); + resolvedTreeSha = + typeof commit.tree?.sha === "string" ? commit.tree.sha : undefined; } } - return typeof cur === "string" ? cur : undefined; - }; - - treeSha = - getNestedString(branch, ["commit", "commit", "tree", "sha"]) || - getNestedString(branch, ["commit", "sha"]); - } catch { - // If resolving the branch fails, fall back to the previous behavior - treeSha = undefined; - } + } catch { + resolvedTreeSha = undefined; + } - const { data: repoTree } = await client.rest.git.getTree({ - owner, - repo, - tree_sha: treeSha ?? repoInfo.default_branch, - }); + if (!resolvedTreeSha) { + throw initialTreeError; + } + + ({ data: repoTree } = await client.rest.git.getTree({ + owner, + repo, + tree_sha: resolvedTreeSha, + })); + } type RepoTreeItem = (typeof repoTree.tree)[number]; const repoContents = repoTree.tree.filter( From 509fe8388717e4a6926299453bb1333a4d788efd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 30 May 2026 18:28:15 +0000 Subject: [PATCH 6/7] chore: address review feedback on octokit tree fallback --- src/lib/octokit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/octokit.ts b/src/lib/octokit.ts index 93d941e..fafc91c 100644 --- a/src/lib/octokit.ts +++ b/src/lib/octokit.ts @@ -168,7 +168,7 @@ export async function getRepoSnapshot( tree_sha: repoInfo.default_branch, })); } catch (initialTreeError: unknown) { - let resolvedTreeSha: string | undefined = undefined; + let resolvedTreeSha: string | undefined; try { const { data: branch } = await client.rest.repos.getBranch({ @@ -192,7 +192,7 @@ export async function getRepoSnapshot( } } } catch { - resolvedTreeSha = undefined; + // Ignore fallback resolution errors and rethrow the original tree lookup error below. } if (!resolvedTreeSha) { From 8579c8d271e6b35eebbb3ef341beaba511bf9806 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 07:22:16 +0000 Subject: [PATCH 7/7] [autofix.ci] apply automated fixes --- src/lib/octokit.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib/octokit.ts b/src/lib/octokit.ts index fafc91c..cd9f7bf 100644 --- a/src/lib/octokit.ts +++ b/src/lib/octokit.ts @@ -139,7 +139,10 @@ export async function getRepoSnapshot( ) { const client = createOctokit(accessToken); - const getNestedString = (obj: unknown, path: string[]): string | undefined => { + const getNestedString = ( + obj: unknown, + path: string[], + ): string | undefined => { let cur: unknown = obj; for (const p of path) { if ( @@ -177,7 +180,12 @@ export async function getRepoSnapshot( branch: repoInfo.default_branch, }); - resolvedTreeSha = getNestedString(branch, ["commit", "commit", "tree", "sha"]); + resolvedTreeSha = getNestedString(branch, [ + "commit", + "commit", + "tree", + "sha", + ]); if (!resolvedTreeSha) { const commitSha = getNestedString(branch, ["commit", "sha"]); @@ -188,7 +196,9 @@ export async function getRepoSnapshot( commit_sha: commitSha, }); resolvedTreeSha = - typeof commit.tree?.sha === "string" ? commit.tree.sha : undefined; + typeof commit.tree?.sha === "string" + ? commit.tree.sha + : undefined; } } } catch {