# How no-mistakes Handles GitHub Fork PR Routing with Different Remotes

> Learn how no-mistakes handles GitHub fork PR routing with different remotes by storing upstream and fork URLs. Route pushes to your fork and PRs to upstream seamlessly.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-07-19

---

**no-mistakes routes pushes to your fork remote while opening pull requests against the upstream repository by storing both `upstream_url` and `fork_url` in the repository record.**

The `no-mistakes` CLI automates Git workflows across repositories with fork-based contributions. When working with GitHub forks, the tool solves the dual-remote problem: pushing code to your personal fork while ensuring pull requests target the original parent repository. This article explains how the source code implements GitHub fork PR routing using provider-specific logic in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) and [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go).

## The Dual-URL Repository Model

`no-mistakes` maintains two distinct URLs for each repository record to handle fork scenarios:

- **`upstream_url`**: The parent repository that receives the pull request base.
- **`fork_url`** (optional): Your personal fork used as the push target.

This separation ensures the **push** operation targets your write-accessible fork while the **PR** operation references the upstream repository as the merge target. When you initialize a repository with a fork URL, the CLI stores both values in the repository configuration.

```bash

# Initialize with explicit fork URL (GitHub only)

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

# Creates record: upstream_url = https://github.com/kunchenguid/no-mistakes.git

#                 fork_url     = https://github.com/myuser/no-mistakes-fork.git

```

## How the Pipeline Detects Fork Configuration

During the **push** step, the pipeline builds an SCM host object based on the provider detected from `upstream_url`. The routing logic lives in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) and executes three specific operations for GitHub repositories.

### Step 1: Extract Host and Slug

The code calls `github.HostPrefixedSlug` to parse the `owner/repo` identifier from `Repo.UpstreamURL`. If the upstream URL is unavailable, it falls back to the PR URL. This extraction handles both GitHub.com and GitHub Enterprise instances by preserving host prefixes when necessary.

### Step 2: Identify Fork Owner

If `Repo.ForkURL` is present, the code invokes `github.RepoSlug` to extract the plain slug (without host prefix) from the fork URL. This value represents the fork owner needed to construct the `--head <fork-owner>:<branch>` argument for GitHub CLI commands.

### Step 3: Initialize Fork-Aware Host

The function returns `github.NewWithFork(cmdFactory, cliAvailable, host, repo, forkRepo)`. This constructor wires the push command to use `fork_url` as the remote target while keeping PR creation anchored to the upstream repository.

```go
// Simplified excerpt from internal/pipeline/steps/host.go
host := scm.ExtractHost(sctx.Repo.UpstreamURL)
repo := github.HostPrefixedSlug(sctx.Repo.UpstreamURL)

forkRepo := ""
if sctx.Repo.ForkURL != "" {
    forkRepo = github.RepoSlug(sctx.Repo.ForkURL)
}

ghHost := github.NewWithFork(cmdFactory, cliAvailable, host, repo, forkRepo)
// Push targets forkRepo; PR uses repo as base and forkRepo as head owner

```

## Provider-Specific Implementation

The fork routing implementation varies by provider. While GitHub receives full support, other providers explicitly disable this feature.

### GitHub Fork Support

In [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go), the `NewWithFork` function creates a host object that handles the split remote workflow. When `forkRepo` is provided, the **push** operation targets the fork URL, while the **PR** command constructs the `--head` argument using the fork owner extracted earlier.

The execution flow becomes:
- **Push**: `git push <fork_url> <branch>`
- **PR**: `gh pr create --repo <upstream> --head <fork-owner>:<branch>`

This guarantees the PR base always points to the parent repository even though the commit lives on a different remote.

### Other Providers

For GitLab, Bitbucket, and Azure DevOps, the code in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) returns explicit errors when `fork_url` is present. Each provider case outputs a message such as **"fork PR routing for GitLab is not implemented"** and skips PR creation entirely to prevent incorrect targeting.

## Practical Configuration and Execution

### Initializing with a Fork

Configure your repository record to support fork workflows using the `--fork-url` flag during initialization. This records both URLs in the internal database, enabling the dual-remote logic for subsequent runs.

```bash
no-mistakes init \
  --upstream-url https://github.com/kunchenguid/no-mistakes.git \
  --fork-url https://github.com/myuser/no-mistakes-fork.git

```

### Runtime Execution Flow

When you execute `no-mistakes axi run`, the pipeline automatically detects the fork configuration and splits the operations:

```bash

# Runtime behavior for configured forks:

# 1. Commit pushed to fork remote

# 2. PR opened against upstream with correct head reference

no-mistakes axi run

```

The tool invokes `gh pr create` with the `--head` parameter formatted as `fork-owner:branch-name`, ensuring GitHub correctly associates the PR with your fork while targeting the upstream base repository.

## Summary

- **Dual URL storage**: `no-mistakes` records `upstream_url` for PR bases and `fork_url` for push targets in the repository configuration.
- **GitHub-specific implementation**: The `NewWithFork` constructor in [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go) wires push operations to the fork while anchoring PRs to the upstream repository.
- **Automatic head construction**: The fork owner extracted via `github.RepoSlug` populates the `--head` argument for GitHub CLI commands.
- **Provider limitations**: GitLab, Bitbucket, and Azure DevOps explicitly disable fork PR routing and return implementation errors when `fork_url` is present.

## Frequently Asked Questions

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

No. According to the source code in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go), `no-mistakes` explicitly disables fork PR routing for GitLab, Bitbucket, and Azure DevOps. When a `fork_url` is present for these providers, the pipeline returns an error message such as "fork PR routing for GitLab is not implemented" and skips PR creation.

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

If `fork_url` is omitted, the push and PR operations both target the `upstream_url`. The `github.NewWithFork` function receives an empty string for the fork parameter, causing the host to use the upstream remote for both pushing and pull request creation, effectively treating the repository as a direct clone rather than a fork workflow.

### How does no-mistakes handle the --head argument for GitHub PRs?

The tool extracts the fork owner from `fork_url` using `github.RepoSlug` in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go). This owner string is passed to `NewWithFork` and subsequently used to construct the `--head` argument in the format `<fork-owner>:<branch>`. This ensures GitHub associates the pull request with the correct source fork while maintaining the upstream repository as the merge target.

### Where is the fork configuration stored in no-mistakes?

The fork configuration persists in the repository record database, specifically in the fields `upstream_url` and `fork_url` as documented in [`AGENTS.md`](https://github.com/kunchenguid/no-mistakes/blob/main/AGENTS.md). These values are read during the push step in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) to determine whether to activate the fork-aware routing logic for GitHub repositories.