# How GitHub Fork Routing Works with Parent Repository and Fork URL Configuration

> Understand GitHub fork routing with kunchenguid/no-mistakes. Learn how upstream and fork URLs direct PRs and pushes using SQLite and its resolve functions.

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

---

**The `no-mistakes` CLI implements GitHub fork routing by storing both an `upstream_url` (parent repository) and an optional `fork_url` in a SQLite database, using `resolveUpstreamURL` to determine PR targets and `resolvePushURL` to determine push destinations.**

The `no-mistakes` tool automates Git workflows while respecting GitHub's fork-based collaboration model. Understanding how it handles **GitHub fork routing** requires examining how it stores and resolves repository relationships in its internal database and pipeline steps. This architecture ensures contributors push to their forks while opening pull requests against the correct parent repository.

## The Dual-URL Database Schema

At the core of the routing mechanism lies the SQLite schema defined in [`internal/db/schema.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/schema.go). The `repos` table maintains two distinct URL columns for every repository:

* **`upstream_url`** — The URL of the parent (original) repository.
* **`fork_url`** — An optional URL of a personal fork that a contributor pushes to.

These columns are defined in lines 8-11 of the schema file. When you run `no-mistakes init --fork-url <url>`, the CLI persists the fork URL in this column, while the upstream URL is automatically extracted from the current clone's `origin` remote.

## Resolving Repository URLs at Runtime

The pipeline logic that determines where to push code or create pull requests resides in [`internal/pipeline/steps/common_git.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/common_git.go). This file implements two critical resolution functions that handle the routing decisions.

### Determining the Upstream Parent

The `resolveUpstreamURL` function (lines 55-73) handles upstream resolution with credential safety in mind. It first attempts to read the `origin` remote from the local worktree, which may contain embedded credentials. If this live remote matches the redacted upstream URL stored in the database, the function returns the credential-rich version from the worktree. Otherwise, it falls back to the database's `upstream_url` value.

This approach ensures that authentication tokens remain only in the local Git configuration while the database stores sanitized URLs.

### Determining the Push Destination

The `resolvePushURL` function (lines 92-97) determines where commits are pushed. It returns the `fork_url` if the repository record contains a non-empty value. If no fork is configured, it falls back to the upstream URL resolved by `resolveUpstreamURL`.

```go
// Example: Resolve the URL to push to
pushURL := resolvePushURL(stepCtx) // returns fork URL if set, otherwise upstream URL
fmt.Println("Push will be made to:", pushURL)

```

## Pull Request Creation and Routing

When creating pull requests, the tool must distinguish between the target repository (parent) and the source repository (fork). The logic documented in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go) (lines 52-66) clarifies that while the push step may use `fork_url`, the PR creation step must target the parent repository.

The GitHub adapter implements this by creating a PR from `<fork_owner>:<branch>` to the parent repository. The code uses `resolveUpstreamURL` to identify the target repository and `resolvePushURL` to identify the head repository:

```go
// Example: Creating a PR on GitHub (simplified)
parent := resolveUpstreamURL(stepCtx)   // target repository (parent)
head   := resolvePushURL(stepCtx)       // fork repository (head)
gh.CreatePR(parent, head, branchName)   // PR created from fork → parent

```

## Initializing Fork Configuration

To configure the fork routing, use the initialization command defined in [`internal/cli/init.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/init.go):

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

```

This command stores the provided URL in the `fork_url` column of the `repos` table. If omitted, the system operates in "direct mode," pushing to and creating PRs against the upstream repository exclusively.

## Summary

The `no-mistakes` fork routing system guarantees three critical behaviors:

* **Correct push destination** — When a fork URL is configured, pushes go to the fork; otherwise, they go to the upstream parent.
* **Accurate PR routing** — Pull requests are always opened against the parent repository, with the head reference pointing to the fork (or the same repo when no fork exists).
* **Credential safety** — Embedded credentials live only in the local `origin` remote; the database stores redacted URLs, and `resolveUpstreamURL` prefers the live remote when it matches the stored value.

## Frequently Asked Questions

### What happens if no fork URL is configured?

If the `fork_url` column is empty, `resolvePushURL` returns the upstream URL, causing both pushes and pull requests to target the parent repository directly. This effectively disables the fork workflow and treats the repository as the canonical source.

### How does no-mistakes handle credentials in repository URLs?

The system stores redacted URLs in the SQLite database (lacking embedded tokens) while reading the actual remote configuration from the local Git worktree. The `resolveUpstreamURL` function compares the worktree's `origin` remote against the stored redacted URL, returning the credential-rich version only when they match, ensuring tokens never persist in the database.

### Can I change the fork URL after initialization?

Yes. Running `no-mistakes init --fork-url <new-url>` updates the `fork_url` column in the database for the current repository. The next pipeline execution will use the new URL for push operations and pull request head references.

### Why does the PR creation step use the parent repository as the target?

According to the implementation in [`internal/pipeline/steps/host.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/host.go), the PR must target the upstream (parent) repository because that is where code review and merging occurs. The fork serves only as the source of changes. This aligns with GitHub's standard fork-and-pull workflow, where contributors push to their fork but request changes be merged into the original repository.