# How Fork Routing Works for GitHub Pull Requests in no-mistakes

> Learn how no-mistakes routes GitHub pull requests via forks. Discover how fork_url and the gh CLI streamline your PR workflow for a no-mistakes experience. Click to find out more.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: deep-dive
- Published: 2026-06-30

---

**no-mistakes routes GitHub pull requests through a contributor's fork by storing an optional `fork_url` in the local database and passing it to the `gh` CLI via the `--head` flag during PR creation.**

The `no-mistakes` toolchain automates pull request workflows against upstream repositories while preserving the standard GitHub fork-and-contribute model. When a repository is initialized with a fork URL, the pipeline automatically constructs SCM commands that target the upstream repository while using the fork's branch as the source.

## Where the Fork URL Is Stored

Fork routing begins with persistence in the local SQLite database. When you run `no-mistakes init --fork-url <url>`, the value is written to the `fork_url` column of the `repos` table defined in [`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go).

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

```

Throughout the pipeline execution, this value is accessible via `sctx.Repo.ForkURL` from the configuration model in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go). If this string is empty, the tool assumes you are working directly against the upstream repository; if populated, it triggers fork-aware host construction.

## Building the SCM Host with Fork Support

The routing decision occurs in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) within the `buildHost` function. For GitHub repositories, the logic resolves both the upstream slug and an optional fork slug before instantiating the host.

```go
case scm.ProviderGitHub:
    // Resolve the owner/name slug …
    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`** captures the upstream repository slug (e.g., `owner/main-repo`).
- **`forkRepo`** captures the fork slug only when `sctx.Repo.ForkURL` is non-empty.

The constructor `github.NewWithFork` in [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go) wires the `gh` CLI to include both `--repo <upstream>` and `--head <fork>` arguments. This instructs GitHub to open a PR from the fork into the upstream repository. If `fork_url` is empty, the code falls back to `github.New`, which omits the `--head` flag and creates the PR from the current checkout.

## PR Creation Without Fork Logic

The actual PR step in [`internal/pipeline/steps/pr.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/pr.go) contains no conditional fork logic. The `Execute` method simply delegates to the host interface.

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

```

Because the host was already configured with fork awareness during the `buildHost` phase, `host.CreatePR` automatically expands to a `gh pr create --repo <upstream> --head <fork>` command when appropriate. This separation of concerns keeps the PR step provider-agnostic while the host encapsulates the GitHub-specific fork routing.

## Provider Limitations and Guardrails

Fork routing is explicitly limited to GitHub. The `buildHost` function contains guardrails for other providers in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go):

```go
case scm.ProviderGitLab:
    if sctx.Repo.ForkURL != "" {
        // Fork PR routing for GitLab is intentionally not half‑wired.
        return nil, "fork PR routing for GitLab is not implemented"
    }
...
case scm.ProviderBitbucket:
    if sctx.Repo.ForkURL != "" {
        // Fork PR routing for Bitbucket is intentionally not half‑wired.
        return nil, "fork PR routing for Bitbucket is not implemented"
    }

```

Attempts to use a `fork_url` with GitLab or Bitbucket result in a clear error message and pipeline skip, preventing half-implemented behavior.

## End-to-End Workflow Example

1. **Initialize the repository** with the contributor's fork URL.
2. **Query the database** to verify persistence.
3. **Execute the pipeline**, which constructs the appropriate `gh` command.

When `sctx.Repo.ForkURL` is present, the effective CLI command generated by the host resembles:

```bash
gh pr create --repo owner/no-mistakes \
             --head jdoe/no-mistakes-fork \
             --base main \
             --title "feat: add X" \
             --body "## What Changed …"

```

## Practical Code Examples

### Initializing a Repository with a Fork URL

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

```

Verify the configuration via the database helper:

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

```

### Running the Pipeline

```bash
no-mistakes run

```

The PR step automatically invokes the fork-aware host logic from [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) (lines 24–37).

### Disabling Fork Routing

To create a PR directly from the current checkout without using the fork:

```go
// Clear the fork URL before a run
repo.ForkURL = ""
db.UpdateRepoForkURL(repo.ID, "")

```

This forces `buildHost` to use `github.New` instead of `github.NewWithFork`, omitting the `--head` flag from the resulting `gh` command.

## Summary

- **Storage**: The `fork_url` column in [`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go) persists the contributor's fork location.
- **Host Construction**: [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) detects the fork URL and instantiates `github.NewWithFork` to enable routing.
- **Command Generation**: The GitHub host in [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go) translates the fork slug into a `--head` flag for the `gh` CLI.
- **Provider Scope**: Fork routing is implemented only for GitHub; GitLab and Bitbucket explicitly reject fork URLs with clear error messages.
- **Step Delegation**: [`internal/pipeline/steps/pr.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/pr.go) remains agnostic of fork logic, relying on the pre-configured host to handle upstream vs. fork targeting.

## Frequently Asked Questions

### Can I use fork routing with GitLab or Bitbucket repositories?

No. As implemented in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go), the `buildHost` function returns an explicit error if `sctx.Repo.ForkURL` is non-empty for GitLab or Bitbucket providers. The error messages state that "fork PR routing for [Provider] is not implemented," preventing ambiguous behavior.

### What happens if I don't specify a fork URL during initialization?

If the `fork_url` column is empty (or cleared via the database), `buildHost` invokes `github.New` instead of `github.NewWithFork`. The resulting PR creation command omits the `--head` flag, causing `gh pr create` to open a pull request from the current local branch directly against the upstream repository.

### How does no-mistakes resolve repository slugs when both upstream and fork URLs are present?

The `buildHost` function in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) uses `github.RepoSlug()` to parse the owner/name pair from both `sctx.Repo.UpstreamURL` (stored as `repo`) and `sctx.Repo.ForkURL` (stored as `forkRepo`). These slugs are passed separately to `github.NewWithFork`, ensuring the `--repo` flag targets the upstream while the `--head` flag references the fork.

### Is it possible to switch between fork and non-fork PR creation for the same repository?

Yes. You can toggle fork routing by updating the `fork_url` value in the database using `db.UpdateRepoForkURL()` or by re-running `no-mistakes init` with or without the `--fork-url` flag. The pipeline reads this value fresh during each `buildHost` invocation, allowing you to alternate between contributing from a fork and pushing directly to the upstream repository.