How no-mistakes Handles Fork Routing for GitHub PRs
no-mistakes maintains separate upstream_url and fork_url fields to route commits to a contributor's fork while ensuring pull requests always target the parent repository.
The no-mistakes automation tool streamlines GitHub workflows by solving a critical routing challenge: pushing code to a personal fork while opening PRs against the upstream repository. Understanding fork routing for GitHub PRs in this codebase requires examining how the system distinguishes between write targets and PR base repositories using a dual-URL architecture.
The Dual-URL Architecture
At the core of the routing logic lies a simple but effective data model that tracks two distinct remote URLs for every repository.
Upstream vs. Fork Remotes
The repos table stores repository metadata using two key fields:
upstream_url: The canonical GitHub repository (the parent repo) that serves as the official source of truth.fork_url: An optional URL pointing to a contributor's personal fork, stored in thefork_urlcolumn.
This separation enables the system to make intelligent decisions about where to send changes based on the operation type.
Push Operations: Routing Commits to the Fork
When propagating branch updates, no-mistakes prioritizes the contributor's fork to avoid requiring write permissions on the upstream repository.
The PushURL Method Implementation
In internal/db/repo.go (lines 19–27), the PushURL method implements the precedence logic:
// PushURL returns the remote URL that should receive branch updates.
func (r *Repo) PushURL() string {
if r == nil {
return ""
}
if strings.TrimSpace(r.ForkURL) != "" {
return r.ForkURL // fork takes precedence
}
return r.UpstreamURL // otherwise push to the parent
}
The method returns r.ForkURL when configured, falling back to r.UpstreamURL only when no fork exists.
Execution in the Push Pipeline
The push step located in internal/pipeline/steps/push.go invokes this logic through sctx.Repo.PushURL(). This ensures that when a contributor runs a push command, the commits land on their fork rather than attempting direct writes to the upstream repository.
Pull Request Creation: Targeting the Upstream Repository
Despite routing pushes to forks, PR creation follows a different security model that always uses the parent repository as the base.
Excluding Fork URLs from PR Commands
The internal/pipeline/steps/host.go file contains guards that prevent direct use of fork_url during PR creation. According to the source code, the fork_url itself is never passed to the gh pr create command. Instead, the tool derives only the fork owner name from the URL.
This design preserves the security model where the maintainer's trusted default-branch configuration supplies the PR target, while the contributor's fork merely supplies the head reference.
Constructing the Head Reference
When a fork is configured, no-mistakes constructs the PR head using the --head <fork_owner>:<branch> syntax. The command structure becomes:
gh pr create \
--repo parent_owner/parent_repo \
--head fork_owner:feature_branch \
--title "Commit message" \
--body "PR description"
This approach directs GitHub to compare the fork's branch against the upstream repository's default branch without requiring the fork URL to be specified as the repository target.
Security and Workflow Implications
The dual-routing strategy provides two critical guarantees:
- Contributor autonomy: Branch updates go to the fork when
fork_urlis set, eliminating the need for write permissions on the upstream repo. - Repository integrity: Pull-request creation always targets the upstream repository, ensuring that maintainers control the merge target and base branch configuration.
Practical Workflow Examples
The following commands demonstrate the fork routing behavior in practice:
# Push a feature branch – it lands on the fork if fork_url is set
$ no-mistakes push --branch feature/foo
# Internally: git push <fork_url> feature/foo
# Open a PR using the upstream repository as the base
$ no-mistakes pr create --title "Add foo" --body "Fixes #123"
# Internally (fork present):
# gh pr create \
# --repo parent_owner/parent_repo \
# --head fork_owner:feature/foo \
# --title "Add foo" \
# --body "Fixes #123"
Summary
no-mistakesstores repository metadata in therepostable usingupstream_urlandfork_urlfields.- The
PushURL()method ininternal/db/repo.goprioritizesfork_urlfor push operations, falling back toupstream_urlwhen empty. - The push step in
internal/pipeline/steps/push.gocallssctx.Repo.PushURL()to determine the remote destination. - PR creation always targets the upstream repository, with
internal/pipeline/steps/host.gopreventing directfork_urlusage. - Fork branches are referenced using
--head <fork_owner>:<branch>syntax rather than specifying the fork URL as the repository target.
Frequently Asked Questions
How does no-mistakes decide which remote receives my commits?
The PushURL() method in internal/db/repo.go checks the fork_url field first. If a non-empty fork URL exists in the repository configuration, commits route there; otherwise, they push to the upstream_url. This logic executes during the push step in internal/pipeline/steps/push.go.
Why does no-mistakes ignore the fork_url when creating pull requests?
The tool intentionally excludes the fork_url from gh pr create commands to maintain security and consistency. Pull requests must target the upstream repository to ensure maintainers control the base branch and merge settings. The fork only contributes the head reference via the --head flag, not the repository target.
What happens if I configure only an upstream_url without a fork_url?
Without a configured fork_url, the PushURL() method returns the upstream_url for all operations. Both pushes and PR creation will reference the parent repository directly, requiring appropriate write permissions for branch creation.
Where is the fork routing logic documented in the source code?
Key implementations reside in three files: internal/db/repo.go defines the PushURL method; internal/pipeline/steps/push.go executes pushes using that URL; and internal/pipeline/steps/host.go handles the PR creation logic that excludes fork URLs from the target repository parameter.
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 →