How no-mistakes Handles Fork URL Routing for GitHub PRs with Parent Repositories

no-mistakes stores an optional fork URL in the repository record and constructs GitHub CLI commands that route pull requests from the fork to the parent repository using the <forkOwner>:<branch> head reference syntax.

no-mistakes is an open-source CI/CD pipeline tool that automates pull request creation across multiple source control providers. When working with forked repositories, the tool implements a dedicated fork URL routing mechanism that allows contributors to open PRs from their personal fork while correctly targeting the upstream parent repository.

Storing Fork URLs in the Database Schema

The fork URL configuration begins with repository initialization. When you run no-mistakes init --fork-url, the tool persists the fork URL in the repos table under the fork_url column. This value is accessed throughout the pipeline via the Repo struct defined in internal/db/repo.go.

type Repo struct {
    ForkURL string   // fork_url column in database
}

According to the source in repo.go, this field remains empty unless explicitly configured, making fork support fully optional for repository records.

Detecting the Provider and Building the Host

When a pipeline run reaches the PR creation step, PRStep.Execute in internal/pipeline/steps/pr.go calls scm.DetectProvider to identify the source control provider from the upstream URL. The step then invokes buildHost (located in internal/pipeline/steps/host.go) to construct a provider-specific host interface.

provider := scm.DetectProvider(sctx.Repo.UpstreamURL)
host, skipReason := buildHost(sctx, provider)   // L48-L50 in host.go

The buildHost function examines sctx.Repo.ForkURL to determine if fork routing is required. For GitHub repositories, it extracts repository slugs from both the upstream URL and the fork URL, passing these to the GitHub-specific constructor.

GitHub-Specific Fork Routing Implementation

For GitHub providers, the buildHost function handles fork URLs by extracting the owner/name slug from both the parent and fork repositories. Located at L24-L36 in host.go, this logic prepares the arguments for github.NewWithFork:

repo := github.RepoSlug(sctx.Repo.UpstreamURL)
if sctx.Repo.ForkURL != "" {
    forkRepo = github.RepoSlug(sctx.Repo.ForkURL)
}
return github.NewWithFork(cmdFactory,
    func() bool { return stepCLIAvailable(sctx, provider) },
    repo, forkRepo), ""

The github.NewWithFork constructor, found in internal/scm/github/github.go, stores the fork owner separately within the Host struct:

h.forkOwner = repoOwner(forkRepo)

This separation allows the host to distinguish between the target repository (parent) and the source repository (fork) throughout the PR creation process.

Creating Pull Requests with Fork Head References

When Host.CreatePR executes, it uses the headRef method to determine the appropriate --head argument for the GitHub CLI. This method checks for the presence of a fork owner and formats the reference accordingly.

In internal/scm/github/github.go, the PR creation command is constructed as follows:

args := append([]string{"pr", "create",
    "--head", h.headRef(branch),
    "--base", base},
    h.repoArgs()...)

The headRef method implements the conditional logic that routes the PR to the correct source:

func (h *Host) headRef(branch string) string {
    if h.forkOwner == "" {
        return branch               // no fork: use branch directly
    }
    return h.forkOwner + ":" + branch // fork: use owner:branch syntax
}

This produces the forkOwner:branch syntax required by GitHub to identify pull request sources from forked repositories, ensuring the PR originates from the contributor's fork while targeting the parent repository's default branch.

Limitations with GitLab and Bitbucket

The fork URL routing implementation is currently specific to GitHub. When buildHost encounters a fork URL configured for GitLab or Bitbucket providers, it returns a skip reason indicating the feature is not yet implemented.

In host.go and L46-L50), the code explicitly handles these cases:

if sctx.Repo.ForkURL != "" {
    return nil, "fork PR routing for GitLab is not implemented"
}

This ensures the pipeline fails gracefully with a clear explanation rather than attempting unsupported fork operations on these providers.

Summary

  • Fork URL Storage: The fork_url column in the repos table stores the optional fork URL, accessible via sctx.Repo.ForkURL throughout the pipeline.
  • Host Construction: buildHost in host.go detects fork configurations and delegates to github.NewWithFork for GitHub repositories.
  • Owner Extraction: The GitHub host stores the fork owner separately from the repository slug, enabling precise PR source routing.
  • Head Reference Format: The headRef method generates <forkOwner>:<branch> syntax for forks, which the GitHub CLI translates to the correct source repository.
  • Provider Limitations: Fork URL routing is only implemented for GitHub; GitLab and Bitbucket repositories with fork URLs are skipped with explanatory error messages.

Frequently Asked Questions

How do I configure a fork URL when initializing a repository?

Use the --fork-url flag with the no-mistakes init command. For example: no-mistakes init --fork-url https://github.com/your-username/no-mistakes.git. This persists the URL in the database and enables fork routing for subsequent PR operations.

Why does no-mistakes skip PR creation when using a fork URL with GitLab?

As implemented in internal/pipeline/steps/host.go, the buildHost function returns a skip reason for GitLab and Bitbucket repositories when a fork URL is present. The code explicitly states that "fork PR routing for GitLab is not implemented," indicating this functionality is currently GitHub-specific.

What is the difference between the head reference for forked and non-forked PRs?

For standard PRs without forks, the head reference is simply the branch name (e.g., feature-branch). For forked repositories, the headRef method in github.go prefixes the branch with the fork owner (e.g., fork-owner:feature-branch). This syntax tells GitHub to look for the branch in the contributor's fork rather than the parent repository.

Which source files handle the fork routing logic?

The primary files involved are internal/db/repo.go (schema definition), internal/pipeline/steps/host.go (provider detection and host building), and internal/scm/github/github.go (GitHub-specific PR creation with fork owner handling). The PR step execution is coordinated from internal/pipeline/steps/pr.go.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →