How the Fork Routing Mechanism Works for GitHub Pull Requests in no‑mistakes

The fork routing mechanism in no‑mistakes stores an optional fork_url in the local database and passes it to the GitHub CLI via the --head flag, allowing pull requests to originate from a contributor’s fork while targeting the upstream repository.

The no‑mistakes open‑source tool automates SCM workflows by abstracting provider‑specific commands into a unified pipeline. Understanding how it handles fork routing for GitHub pull requests is essential for contributors who work across multiple remotes and need to submit changes from personal forks rather than the primary repository.

Storing the Fork Configuration

When you initialize a repository, no‑mistakes persists an optional fork_url in its local SQLite database. This value determines whether subsequent pull requests route through a fork or originate from the current checkout.

In internal/db/repo.go, the schema stores the fork reference alongside the upstream URL:

INSERT INTO repos (id, working_path, upstream_url, fork_url, default_branch, created_at) …

You populate this column using the CLI:

no-mistakes init --fork-url https://github.com/your-username/no-mistakes-fork.git

The configuration layer in internal/config/config.go exposes this value via the Repo struct as sctx.Repo.ForkURL, making it available throughout the pipeline execution context.

Building the SCM Host with Fork Support

The decision to route through a fork occurs in internal/pipeline/steps/host.go within the buildHost function. This step constructs a provider‑specific scm.Host implementation based on the detected SCM provider.

For GitHub, the logic resolves two repository slugs:

case scm.ProviderGitHub:
    repo := github.RepoSlug(sctx.Repo.UpstreamURL)
    if repo == "" && sctx.Run.PRURL != nil {
        repo = github.RepoSlug(*sctx.Run.PRURL)
    }
    forkRepo := ""
    if sctx.Repo.ForkURL != "" {
        forkRepo = github.RepoSlug(sctx.Repo.ForkURL)   // ← fork routing
    }
    return github.NewWithFork(cmdFactory,
        func() bool { return stepCLIAvailable(sctx, provider) },
        repo, forkRepo), ""
  • repo – The slug of the upstream repository (e.g., owner/main-repo).
  • forkRepo – The slug extracted from ForkURL when present.

The constructor github.NewWithFork (defined in internal/scm/github/github.go) initializes the host with both slugs. When forkRepo is non‑empty, the host configures the underlying gh CLI to append the --head <fork> argument to all PR creation commands. If fork_url is empty, the pipeline falls back to github.New, which omits the --head flag and creates PRs directly from the local checkout.

Executing the Pull Request Step

The PR step itself (internal/pipeline/steps/pr.go) remains agnostic to fork routing. It delegates all provider‑specific logic to the host built by the previous step:

created, err := host.CreatePR(ctx, branch,
    sctx.Repo.DefaultBranch, scm.PRContent(content))

When a fork is configured, host.CreatePR translates this call into a gh pr create command that explicitly targets the upstream repository while specifying the fork as the head:

gh pr create --repo owner/no-mistakes \
             --head your-username/no-mistakes-fork \
             --base main \
             --title "feat: add feature" \
             --body "Description of changes"

This matches the standard GitHub workflow where the PR base is the upstream default branch and the compare branch resides in the contributor’s fork.

Provider Limitations and Guardrails

The fork routing mechanism is currently implemented only for GitHub. The buildHost function contains explicit guardrails for other providers in internal/pipeline/steps/host.go:

case scm.ProviderGitLab:
    if sctx.Repo.ForkURL != "" {
        return nil, "fork PR routing for GitLab is not implemented"
    }
...
case scm.ProviderBitbucket:
    if sctx.Repo.ForkURL != "" {
        return nil, "fork PR routing for Bitbucket is not implemented"
    }

Attempts to configure a fork_url with GitLab or Bitbucket result in an immediate error with a clear log message, preventing half‑wired configurations.

Practical Configuration Examples

Initializing a Repository with Fork Support

To enable fork routing, provide the fork URL during initialization:

no-mistakes init --fork-url https://github.com/jdoe/no-mistakes-fork.git

Verify the configuration programmatically:

repo, _ := db.GetRepoByPath("./")
fmt.Println(repo.ForkURL) // → https://github.com/jdoe/no-mistakes-fork.git

Running a Pipeline That Creates a Fork‑Based PR

Execute the standard run command:

no-mistakes run

When the pipeline reaches the PR step, buildHost detects the configured ForkURL and invokes github.NewWithFork. The effective GitHub CLI command constructed by the host includes both the upstream target and the fork head, creating a cross‑repository pull request automatically.

Disabling Fork Routing for Direct PRs

To create a PR directly from the current repository without using a fork, clear the stored URL:

repo.ForkURL = ""
db.UpdateRepoForkURL(repo.ID, "")

Subsequent runs will invoke github.New instead of github.NewWithFork, generating gh pr create commands without the --head flag.

Summary

  • no‑mistakes stores the optional fork_url in the repos table via internal/db/repo.go and exposes it through sctx.Repo.ForkURL.
  • The buildHost function in internal/pipeline/steps/host.go detects a non‑empty fork URL and instantiates the GitHub host using github.NewWithFork.
  • When configured, the host passes the --head <fork> flag to the gh CLI, routing the pull request from the fork into the upstream repository.
  • The PR step in internal/pipeline/steps/pr.go delegates to the host, requiring no special logic for fork handling.
  • Fork routing is GitHub‑only; GitLab and Bitbucket explicitly error if a fork_url is configured.

Frequently Asked Questions

How do I configure no‑mistakes to use my fork for pull requests?

Run the initialization command with the --fork-url flag: no-mistakes init --fork-url https://github.com/yourusername/repo-fork.git. This persists the URL in the local database, and subsequent PR steps automatically route through your fork using the --head argument with the GitHub CLI.

What happens if I don’t specify a fork URL?

If the fork_url column is empty, buildHost calls github.New instead of github.NewWithFork. The resulting gh pr create command omits the --head flag, creating the pull request from the current branch in the upstream repository rather than from a fork.

Does fork routing work with GitLab or Bitbucket repositories?

No. As implemented in internal/pipeline/steps/host.go, the tool returns an explicit error message ("fork PR routing for GitLab is not implemented" or the Bitbucket equivalent) if a fork_url is configured for non‑GitHub providers. This prevents incomplete workflow implementations.

Which source files control the fork routing logic?

The primary files are internal/pipeline/steps/host.go (host construction and fork detection), internal/scm/github/github.go (the NewWithFork constructor and CLI flag generation), and internal/db/repo.go (persistence of the fork_url column). The PR execution logic resides in 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 →