From 870b29ec17ff6a30307c3a8c4eb9165160e3fc51 Mon Sep 17 00:00:00 2001 From: kevinWangSheng Date: Wed, 25 Mar 2026 21:54:11 -0700 Subject: [PATCH] fix(github): guard against null headRepository on fork PRs When a PR comes from a fork that has been deleted, the GraphQL API returns null for headRepository. The action and CLI both accessed headRepository.nameWithOwner without a null check, causing a crash: null is not an object (evaluating 'prData.headRepository.nameWithOwner') Add optional chaining on the comparison and an explicit error with a clear message in checkoutForkBranch when headRepository is missing. Fixes #19019 --- github/index.ts | 9 ++++++--- packages/opencode/src/cli/cmd/github.ts | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/github/index.ts b/github/index.ts index 1a0a992622..cd61ce9ad8 100644 --- a/github/index.ts +++ b/github/index.ts @@ -72,7 +72,7 @@ type GitHubPullRequest = { } headRepository: { nameWithOwner: string - } + } | null commits: { totalCount: number nodes: Array<{ @@ -163,8 +163,8 @@ try { // 3. Fork PR if (isPullRequest()) { const prData = await fetchPR() - // Local PR - if (prData.headRepository.nameWithOwner === prData.baseRepository.nameWithOwner) { + // Local PR (headRepository is null when the fork has been deleted) + if (prData.headRepository?.nameWithOwner === prData.baseRepository.nameWithOwner) { await checkoutLocalBranch(prData) const dataPrompt = buildPromptDataForPR(prData) const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles) @@ -699,6 +699,9 @@ async function checkoutForkBranch(pr: GitHubPullRequest) { const localBranch = generateBranchName("pr") const depth = Math.max(pr.commits.totalCount, 20) + if (!pr.headRepository?.nameWithOwner) { + throw new Error("Cannot checkout fork branch: headRepository is not available (the fork may have been deleted)") + } await $`git remote add fork https://github.com/${pr.headRepository.nameWithOwner}.git` await $`git fetch fork --depth=${depth} ${remoteBranch}` await $`git checkout -b ${localBranch} fork/${remoteBranch}` diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index edd9d75610..6b0ec13711 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -94,7 +94,7 @@ type GitHubPullRequest = { } headRepository: { nameWithOwner: string - } + } | null commits: { totalCount: number nodes: Array<{ @@ -606,8 +606,8 @@ export const GithubRunCommand = cmd({ issueEvent?.issue.pull_request ) { const prData = await fetchPR() - // Local PR - if (prData.headRepository.nameWithOwner === prData.baseRepository.nameWithOwner) { + // Local PR (headRepository is null when the fork has been deleted) + if (prData.headRepository?.nameWithOwner === prData.baseRepository.nameWithOwner) { await checkoutLocalBranch(prData) const head = await gitText(["rev-parse", "HEAD"]) const dataPrompt = buildPromptDataForPR(prData) @@ -1114,6 +1114,9 @@ export const GithubRunCommand = cmd({ const localBranch = generateBranchName("pr") const depth = Math.max(pr.commits.totalCount, 20) + if (!pr.headRepository?.nameWithOwner) { + throw new Error("Cannot checkout fork branch: headRepository is not available (the fork may have been deleted)") + } await gitRun(["remote", "add", "fork", `https://github.com/${pr.headRepository.nameWithOwner}.git`]) await gitRun(["fetch", "fork", `--depth=${depth}`, remoteBranch]) await gitRun(["checkout", "-b", localBranch, `fork/${remoteBranch}`])