# How Fork Routing Works for GitHub Fork Contributions in no-mistakes

> Understand how fork routing works for GitHub fork contributions in no-mistakes. Learn to separate push targets from PR bases and route branches efficiently to your fork.

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

---

**Fork routing in no-mistakes separates the push target from the PR base by storing an optional `fork_url` alongside the `upstream_url`, using `Repo.PushURL()` to route branches to the contributor's fork while creating pull requests against the parent repository via the `gh pr create --head` flag.**

Fork routing for GitHub fork contributions in no-mistakes enables contributors to push branches to their personal forks while automatically opening pull requests against the upstream repository. This workflow relies on a dual-URL data model that distinguishes between the source of truth and the contributor's working copy. The implementation handles the complexity of GitHub's cross-repository PR workflow transparently through specific database fields and pipeline steps.

## Understanding the Fork Routing Data Model

The system represents a repository using three related pieces of data defined in [`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go):

| Field | Purpose |
|-------|---------|
| `repos.upstream_url` | URL of the **parent** repository that serves as the source of truth for PR base routing. |
| `repos.fork_url` | Optional URL of a **fork** owned by the contributor; this becomes the target for all branch pushes. |
| `Repo.PushURL()` | Helper method that returns `fork_url` when present, otherwise falls back to `upstream_url`【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/db/repo.go#L19-L27】. |

The `PushURL()` method implements the core routing logic:

```go
// Simplified from internal/db/repo.go
func (r *Repo) PushURL() string {
    if r.ForkURL != "" {
        return r.ForkURL
    }
    return r.UpstreamURL
}

```

This abstraction ensures that push operations always target the contributor's fork when configured, while maintaining the upstream repository as the default fallback.

## Initializing a Repository with Fork Support

To enable fork routing, contributors run the `init` command with the `--fork-url` flag:

```bash
no-mistakes init --fork-url git@github.com:myuser/project-fork.git

```

This command records the fork URL in the database while keeping the `origin` remote pointing at the parent repository. As documented in [`AGENTS.md`](https://github.com/kunchenguid/no-mistakes/blob/main/AGENTS.md), if the fork URL is omitted, the command preserves any existing fork URL for idempotent refresh operations【/cache/repos/github.com/kunchenguid/no-mistakes/main/AGENTS.md#L17-L22】.

## Push Target Resolution

All push operations in the pipeline use the `PushURL()` method to determine the remote destination. In [`internal/pipeline/steps/push.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/push.go), the push step obtains the URL via `sctx.Repo.PushURL()` (approximately line 65)【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/pipeline/steps/push.go (line ≈ 65)】.

When a fork URL is configured, this ensures that branch updates land in the contributor's fork rather than directly in the upstream repository. The push step remains agnostic to whether it is targeting a fork or the upstream repository—it simply uses the resolved URL.

## GitHub Pull Request Creation

The GitHub host builder in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) handles the cross-repository PR creation. The `buildHost` function extracts the fork owner from `repos.fork_url` using `github.RepoSlug`, then constructs a host configuration that:

1. Sets `--repo` to the **parent** repository (`repos.upstream_url`).
2. Supplies `--head <fork_owner>:<branch>` to the `gh pr create` command, where `<fork_owner>` comes from the fork URL slug (the owner/component without the host prefix)【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/pipeline/steps/host.go#L41-L47】.

This approach opens a PR **from the contributor's fork** back to the parent repository while the underlying push target remains the fork URL. The resulting command structure is equivalent to:

```bash
gh pr create \
  --repo owner/project \
  --head myuser:my-feature-branch

```

Tests in [`internal/db/repo_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo_test.go) verify that `PushURL()` correctly prefers `fork_url` over `upstream_url` when both are present【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/db/repo_test.go#L59-L77】.

## Provider Limitations

For GitLab, Bitbucket, and Azure DevOps, the code explicitly disables fork-based PR routing. While the push step may still utilize `fork_url` for branch pushes, PR creation is skipped entirely for these providers【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/pipeline/steps/host.go#L48-L66】. This restriction reflects the current GitHub-specific implementation of fork handling.

## Summary

- **Dual-URL architecture**: `upstream_url` defines the PR base, while `fork_url` (optional) defines the push target.
- **Automatic routing**: The `Repo.PushURL()` method in [`internal/db/repo.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go) transparently selects the fork URL when available.
- **GitHub-specific PR creation**: Uses `--head <fork_owner>:<branch>` to create cross-repository PRs while keeping `--repo` pointed at the upstream.
- **Provider scope**: Full fork routing is implemented only for GitHub; other providers lack PR creation support for forks.

## Frequently Asked Questions

### How do I configure no-mistakes to push to my fork while opening PRs against the parent repository?

Run `no-mistakes init --fork-url git@github.com:yourusername/fork.git` to record your fork URL. The system will automatically route pushes to your fork and create pull requests against the upstream repository using the `gh pr create --head` syntax. This separation is handled internally by the `PushURL()` method and the GitHub host builder.

### What happens if I initialize without specifying a fork URL?

If you omit the `--fork-url` flag during initialization, the command preserves any existing fork URL already stored in the database. If no fork URL exists, all operations target the upstream repository directly. This behavior ensures idempotent configuration refreshes as described in the project documentation.

### Why does no-mistakes use the `--head` flag instead of changing the `--repo` target?

The `--head` flag specifies the source branch for the pull request (including the fork owner), while `--repo` remains fixed on the upstream repository to define the PR's destination. This matches GitHub's CLI semantics for cross-repository contributions: the PR is created *in* the upstream repo *from* the fork branch. Hardcoding `--repo` to the upstream URL ensures the PR lands in the correct project regardless of where the branch was pushed.

### Which Git providers support fork routing for PR creation?

Currently, only **GitHub** supports full fork routing including automated PR creation. For GitLab, Bitbucket, and Azure DevOps, the push step can still target a fork URL if configured, but the PR creation step is explicitly skipped in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go). Support for additional providers would require implementing provider-specific slug extraction and PR creation logic similar to the GitHub implementation.