gitbutlerapp/gitbutler · error

Failed to get PR base repo

Error message

Failed to get PR base repo: {}

What it means

get_pull_request_base_repo_url fetches a pull request to determine the git URL of its base repository. A non-success HTTP status from /repos/{owner}/{repo}/pulls/{n} bails with this message and the status. Means the PR could not be read (doesn't exist or inaccessible).

Solutions

  1. Confirm the PR exists: curl the same /pulls/{n} endpoint with the token
  2. Check HTTP status in the message: 404 → wrong repo/PR, 403 → permissions or rate limit, 401 → token invalid
  3. Regenerate or rescope the GitHub token if 401/403
Defensive patterns

Strategy: try-catch

Try / catch

match client.get_pull_request_base_repo_url(owner, repo, pr).await {
    Ok(url) => url,
    Err(e) if e.to_string().contains("404") => {
        eprintln!("PR #{pr} not found in {owner}/{repo}");
        Default::default()
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling get_pull_request_base_repo_url for a PR number that doesn't exist in owner/repo, a private repo the token can't read, or an API error (403 rate limit, 5xx).

Common situations: Wrong owner/repo pair for the PR number; stale PR reference after deletion; token expired or scope-stripped; rate limiting on shared CI tokens.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/c867f806e3f780da. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-github/src/client.rs:437

        struct Base {
            #[serde(default)]
            repo: Option<Repo>,
        }
        #[derive(Debug, Deserialize)]
        struct Repo {
            #[serde(default)]
            git_url: Option<String>,
            #[serde(default)]
            clone_url: Option<String>,
        }

        let url = format!(
            "{}/repos/{}/{}/pulls/{}",
            self.base_url, owner, repo, pr_number
        );
        let response = self.client.get(&url).send().await?;
        if !response.status().is_success() {
            bail!("Failed to get PR base repo: {}", response.status());
        }
        let body: BaseRepoResponse = response.json().await?;
        // git_url matches what TS's parseRemoteUrl sees for the
        // project's own base remote.
        Ok(body.base.repo.and_then(|r| r.git_url.or(r.clone_url)))
    }

    /// Fetch just the GitHub-computed merge status for a PR
    /// (`mergeable_state` and the comment count). These fields are
    /// expensive enough that GitHub recomputes them per call, so we
    /// expose them through a focused endpoint that the UI only
    /// subscribes to where it actually displays them.
    pub async fn get_pull_request_merge_status(
        &self,
        owner: &str,
        repo: &str,
        pr_number: i64,
    ) -> Result<PullRequestMergeStatus> {

View on GitHub (pinned to 58e5313667)