How GitHub Fork Routing for Pull Requests Works in no-mistakes
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:
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 (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.
// 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 and documented in 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:
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 (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.
// 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:
-
Host Step (
internal/pipeline/steps/host.go): Determines the push target by checkingrepo.ForkURLbefore executing git commands. -
GitHub Adapter (
internal/scm/github/github.go): Implements the PR creation logic, guaranteeing--repopoints to upstream while formatting--headwith the fork owner prefix. -
End-to-End Tests (
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 (line 23).
Summary
- Dual URL storage:
no-mistakestracksupstream_urlfor PR bases andfork_urlfor 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, 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 (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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →