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

> Understand GitHub fork routing for pull requests with no-mistakes. Learn how pushes target your fork while PRs use the upstream remote efficiently.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-07-13

---

**The no-mistakes CLI routes pushes to your fork remote while keeping pull request operations anchored to the upstream repository, using `repos.fork_url` for branch pushes and `repos.upstream_url` for PR base targets.**

The `no-mistakes` tool implements a specialized **fork routing process** that manages the complexity of contributing to open-source projects via GitHub forks. By storing dual repository URLs and orchestrating distinct push and PR creation flows, it ensures your code lands on the correct remote while maintaining the proper upstream relationship for pull requests.

## Repository Configuration: Upstream vs. Fork

The architecture relies on two distinct URL fields stored per repository:

- **`repos.upstream_url`**: The parent repository that serves as the source of truth for the PR base branch.
- **`repos.fork_url`** (optional): Your personal fork where branches are pushed during development.

Configure fork routing during initialization:

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

```

When `fork_url` is present, the CLI activates GitHub-specific routing logic that separates push targets from PR bases.

## The Two-Step Routing Process

Once configured, `no-mistakes` executes two distinct flows for every contribution:

### Push Flow: Targeting the Fork

All branch pushes route exclusively to the fork remote. According to the source code in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) (lines 51‑80), the push step reads `repos.fork_url` and pushes to `refs/remotes/no‑mistakes‑push/<branch>`, ensuring your work never accidentally lands on the upstream repository.

```go
// Simplified logic from internal/pipeline/steps/host.go
if repo.ForkURL != "" {
    pushTarget := repo.ForkURL
    // Execute git push to fork remote
}

```

### PR Creation Flow: Anchoring to Upstream

When creating pull requests, the tool maintains the upstream repository as the base while referencing your fork as the head. As implemented in [`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go) and documented in [`AGENTS.md`](https://github.com/kunchenguid/no-mistakes/blob/main/AGENTS.md) (line 22), the GitHub adapter keeps the `--repo` flag pointed at the parent (`repos.upstream_url`) and supplies `--head <fork-owner>:<branch>`.

This generates a command structure like:

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

```

### PR Lookup Flow: Bare Branch Filtering

When checking for existing PRs, the tool avoids owner-qualified head references that would break GitHub CLI semantics. According to [`internal/scm/github/github_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github_test.go) (lines 275‑279), the implementation lists PRs by the **bare branch name** and filters results by `headRepositoryOwner` locally, never passing an owner-qualified head to `gh pr list --head`.

```go
// From internal/scm/github/github.go (test)
cmd := exec.Command("gh", "pr", "list",
    "--head", branch,            // bare branch name only
    "--base", base,
    "--repo", upstreamRepo,
    "--state", "open",
    "--json", "number,url,headRefName,headRepositoryOwner")

```

## Implementation Details

The complete routing lifecycle involves these key components:

1. **Host Step** ([`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go)): Determines the push target by checking `repo.ForkURL` before executing git commands.

2. **GitHub Adapter** ([`internal/scm/github/github.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go)): Implements the PR creation logic, guaranteeing `--repo` points to upstream while formatting `--head` with the fork owner prefix.

3. **End-to-End Tests** ([`internal/e2e/fork_routing_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/e2e/fork_routing_test.go)): Validates that PR lookups do not use owner-qualified heads, ensuring compatibility with GitHub CLI expectations.

## Provider Limitations

Because this fork routing logic is tightly coupled to GitHub CLI (`gh`) semantics, other providers including GitLab, Bitbucket, and Azure DevOps are deliberately **out of scope**. If a legacy record contains a `fork_url` for non-GitHub hosts, PR creation is skipped entirely rather than opening a self-PR, as noted in [`AGENTS.md`](https://github.com/kunchenguid/no-mistakes/blob/main/AGENTS.md) (line 23).

## Summary

- **Dual URL storage**: `no-mistakes` tracks `upstream_url` for PR bases and `fork_url` for push targets.
- **Separate flows**: Pushes go to the fork remote while PR operations remain anchored to the upstream repository.
- **GitHub-specific**: The routing relies on GitHub CLI conventions (`--head owner:branch`) and is not supported for other Git providers.
- **Bare branch filtering**: Existing PR lookups use unqualified branch names and filter by owner programmatically to match GitHub CLI requirements.

## Frequently Asked Questions

### What happens if I don't configure a fork URL?

If `repos.fork_url` is empty, `no-mistakes` operates in standard mode, pushing branches and creating PRs against the upstream repository directly. This works for maintainers with write access but will fail for contributors without push permissions to the parent repository.

### Why does the PR lookup use bare branch names instead of owner-qualified references?

The GitHub CLI (`gh`) expects unqualified branch names for the `--head` flag in `gh pr list`. Owner-qualified references work for `gh pr create` but break the listing command. The tool queries by bare branch name and filters the JSON results by `headRepositoryOwner` to find the correct PR without triggering CLI errors.

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

No. The fork routing implementation is specifically designed for GitHub's CLI semantics. As documented in [`AGENTS.md`](https://github.com/kunchenguid/no-mistakes/blob/main/AGENTS.md), if a `fork_url` exists for non-GitHub providers, the tool skips PR creation entirely to avoid opening self-PRs against the wrong repository.

### How does the tool prevent pushing directly to upstream when a fork is configured?

The push step in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) (lines 51‑80) explicitly checks for `repo.ForkURL` and redirects the push target to the fork remote. This ensures that even if your local git remote points to upstream, the `no-mistakes` pipeline overrides the destination to protect the parent repository from accidental pushes.