# How Fork Routing for GitHub PRs Works in no-mistakes with Separate Origin/Fork Remotes

> Learn how no-mistakes handles GitHub PRs with separate origin and fork remotes. Discover how to push to your fork and create PRs efficiently against the upstream. Boost your workflow now.

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

---

**no-mistakes implements fork routing by distinguishing between `origin` (upstream) and `fork` remotes, ensuring pushes target the contributor's fork while pull requests are created against the upstream repository using the `--repo` and `--head <fork-owner>:<branch>` flags.**

Contributing to open-source projects typically requires maintaining separate remotes for the upstream repository and your personal fork. The `no-mistakes` tool automates **fork routing for GitHub PRs** by intelligently routing git pushes to your fork while ensuring pull request operations target the canonical upstream repository. This architecture prevents permission errors and ensures CI workflows run on the correct repository.

## Understanding the Fork Routing Architecture

### Repository Metadata and Push URL Resolution

In [`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go) (lines 19-27), the `Repo.PushURL()` method determines where to push branch updates. It checks the `ForkURL` field first, falling back to `UpstreamURL` when no fork is configured. This ensures that when you run push commands, updates are sent to your personal fork rather than the upstream repository.

### GitHub Host Handling and CLI Argument Construction

The `github.Host` struct in [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go) (lines 47-55) manages GitHub CLI (`gh`) argument generation. When initialized via `NewWithFork`, it stores the fork owner separately from the upstream slug. The `headRef` method composes references as `<owner>:<branch>`, while `repoArgs` injects `--repo <upstream-slug>` to ensure all operations target the canonical repository.

## How PR Creation and Lookup Handle Forks

### Creating PRs Against Upstream with Fork Branches

The `CreatePR` method in [`github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/github.go) (lines 18-22) constructs the CLI command to open pull requests against the upstream repository while using the fork as the source. When a fork is present, the command renders:

```bash
gh pr create --repo owner/project \
             --head fork-owner:feature-branch \
             --base main

```

This pattern ensures the PR is opened **against the upstream repository** but references the fork's branch as the head.

### Finding Existing PRs with Owner Validation

The `FindPR` method (lines 58-68) queries existing pull requests using `gh pr list --head <branch>` while passing `--repo <upstream>`. When a fork owner is configured, it requests additional JSON fields including `headRepositoryOwner` and validates matches using the `matchesHead` function. This prevents false positives when multiple forks use identical branch names.

## The Complete Fork Routing Workflow

The fork routing implementation follows this execution flow:

1. **Initialization**: Run `no-mistakes init --fork-url <fork-url>` to record the fork URL in the database (`Repo.ForkURL`).

2. **Push Operations**: Any push uses `Repo.PushURL()`, automatically routing to the fork remote when present.

3. **PR Creation**: The system builds a `github.Host` instance with `NewWithFork`, rendering the head reference as `<fork-owner>:<branch>` while maintaining `--repo <upstream-slug>`.

4. **PR Discovery**: `FindPR` filters results by both branch name and fork owner, using the `headRepositoryOwner` field to validate correct matches.

## Implementation Examples

### Pushing to the Correct Remote

When executing push operations, the pipeline uses `Repo.PushURL()` to determine the target:

```go
// internal/pipeline/steps/push.go
pushURL := sctx.Repo.PushURL() // Returns fork URL if set, otherwise upstream
gitCmd := exec.CommandContext(ctx, "git", "push", pushURL, 
    fmt.Sprintf("%s:%s", localBranch, remoteBranch))

```

### Creating a PR with Fork Context

The PR creation step initializes the host with fork awareness:

```go
// internal/pipeline/steps/pr.go
func createPR(ctx context.Context, sctx *StepContext, branch, base string, title, body string) error {
    // Host built with fork owner when fork URL exists
    host := github.NewWithFork(cmdFactory, cliAvailable, hostName, 
        upstreamSlug, forkSlug)
    
    content := scm.PRContent{Title: title, Body: body}
    
    // Head renders as <fork-owner>:<branch>
    _, err := host.CreatePR(ctx, branch, base, content)
    return err
}

```

### Finding Existing PRs

To locate existing pull requests with fork validation:

```go
func findExistingPR(ctx context.Context, host *github.Host, branch, base string) (*scm.PR, error) {
    // Adds extra JSON fields when fork owner is set
    pr, err := host.FindPR(ctx, branch, base)
    if err != nil {
        return nil, err
    }
    return pr, nil
}

```

## Key Source Files

The fork routing logic spans these critical files:

- **[`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go)**: Implements `PushURL()` to select between fork and upstream remotes.
- **[`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go)**: Contains `NewWithFork`, `CreatePR`, and `FindPR` methods handling `--repo` and `--head` flags.
- **[`AGENTS.md`](https://github.com/kunchenguid/no-mistakes/blob/main/AGENTS.md)**: Documents the fork routing policy requiring `--repo` for upstream and `--head <fork_owner>:<branch>` syntax.
- **[`internal/pipeline/steps/push.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/push.go)**: Demonstrates `PushURL()` usage for routing pushes to the correct remote.

## Summary

- **no-mistakes** distinguishes between upstream (`origin`) and contributor fork remotes through the `Repo.PushURL()` method in [`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go).
- Push operations automatically target the fork when configured, preventing unauthorized upstream writes.
- PR creation uses `--repo <upstream>` combined with `--head <fork-owner>:<branch>` to open requests against the canonical repository while referencing fork branches.
- The `FindPR` method validates PR matches against `headRepositoryOwner` to avoid collisions when multiple forks share branch names.
- This architecture supports the standard GitHub workflow where contributors push to forks but integrate via the upstream repository.

## Frequently Asked Questions

### How does no-mistakes determine whether to push to origin or fork?

The tool checks `Repo.ForkURL` in [`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go). If a fork URL is configured via `no-mistakes init --fork-url`, `PushURL()` returns that URL; otherwise, it falls back to the upstream URL. This ensures contributors never accidentally push directly to the canonical repository when they intended to update their fork.

### Why does the PR creation command use --head with a prefix?

When a fork is configured, `github.Host` renders the head reference as `<fork-owner>:<branch>` (e.g., `contributor:feature-branch`). This syntax tells GitHub to look for the branch in the specified fork rather than the upstream repository, while the `--repo` flag ensures the PR itself is opened against the upstream project.

### How does no-mistakes prevent matching PRs from the wrong fork?

The `FindPR` method in [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go) requests the `headRepositoryOwner` field when listing PRs and validates it against the stored fork owner using the `matchesHead` function. This filters out PRs from other forks that might use identical branch names, ensuring the daemon only identifies the correct pull request.

### Where is the fork routing policy documented?

The high-level behavior is specified in [`AGENTS.md`](https://github.com/kunchenguid/no-mistakes/blob/main/AGENTS.md) under the Fork Routing section, which mandates that PR commands must always reference the upstream repository via `--repo` and use the fork owner only in the head reference. The implementation details live in [`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go) for URL resolution and [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go) for CLI argument construction.