How no-mistakes Handles GitHub Fork PR Routing with Different Remotes

no-mistakes routes pushes to your fork remote while opening pull requests against the upstream repository by storing both upstream_url and fork_url in the repository record.

The no-mistakes CLI automates Git workflows across repositories with fork-based contributions. When working with GitHub forks, the tool solves the dual-remote problem: pushing code to your personal fork while ensuring pull requests target the original parent repository. This article explains how the source code implements GitHub fork PR routing using provider-specific logic in internal/pipeline/steps/host.go and internal/scm/github/github.go.

The Dual-URL Repository Model

no-mistakes maintains two distinct URLs for each repository record to handle fork scenarios:

  • upstream_url: The parent repository that receives the pull request base.
  • fork_url (optional): Your personal fork used as the push target.

This separation ensures the push operation targets your write-accessible fork while the PR operation references the upstream repository as the merge target. When you initialize a repository with a fork URL, the CLI stores both values in the repository configuration.


# Initialize with explicit fork URL (GitHub only)

no-mistakes init --fork-url https://github.com/myuser/no-mistakes-fork.git

# Creates record: upstream_url = https://github.com/kunchenguid/no-mistakes.git

#                 fork_url     = https://github.com/myuser/no-mistakes-fork.git

How the Pipeline Detects Fork Configuration

During the push step, the pipeline builds an SCM host object based on the provider detected from upstream_url. The routing logic lives in internal/pipeline/steps/host.go and executes three specific operations for GitHub repositories.

Step 1: Extract Host and Slug

The code calls github.HostPrefixedSlug to parse the owner/repo identifier from Repo.UpstreamURL. If the upstream URL is unavailable, it falls back to the PR URL. This extraction handles both GitHub.com and GitHub Enterprise instances by preserving host prefixes when necessary.

Step 2: Identify Fork Owner

If Repo.ForkURL is present, the code invokes github.RepoSlug to extract the plain slug (without host prefix) from the fork URL. This value represents the fork owner needed to construct the --head <fork-owner>:<branch> argument for GitHub CLI commands.

Step 3: Initialize Fork-Aware Host

The function returns github.NewWithFork(cmdFactory, cliAvailable, host, repo, forkRepo). This constructor wires the push command to use fork_url as the remote target while keeping PR creation anchored to the upstream repository.

// Simplified excerpt from internal/pipeline/steps/host.go
host := scm.ExtractHost(sctx.Repo.UpstreamURL)
repo := github.HostPrefixedSlug(sctx.Repo.UpstreamURL)

forkRepo := ""
if sctx.Repo.ForkURL != "" {
    forkRepo = github.RepoSlug(sctx.Repo.ForkURL)
}

ghHost := github.NewWithFork(cmdFactory, cliAvailable, host, repo, forkRepo)
// Push targets forkRepo; PR uses repo as base and forkRepo as head owner

Provider-Specific Implementation

The fork routing implementation varies by provider. While GitHub receives full support, other providers explicitly disable this feature.

GitHub Fork Support

In internal/scm/github/github.go, the NewWithFork function creates a host object that handles the split remote workflow. When forkRepo is provided, the push operation targets the fork URL, while the PR command constructs the --head argument using the fork owner extracted earlier.

The execution flow becomes:

  • Push: git push <fork_url> <branch>
  • PR: gh pr create --repo <upstream> --head <fork-owner>:<branch>

This guarantees the PR base always points to the parent repository even though the commit lives on a different remote.

Other Providers

For GitLab, Bitbucket, and Azure DevOps, the code in internal/pipeline/steps/host.go returns explicit errors when fork_url is present. Each provider case outputs a message such as "fork PR routing for GitLab is not implemented" and skips PR creation entirely to prevent incorrect targeting.

Practical Configuration and Execution

Initializing with a Fork

Configure your repository record to support fork workflows using the --fork-url flag during initialization. This records both URLs in the internal database, enabling the dual-remote logic for subsequent runs.

no-mistakes init \
  --upstream-url https://github.com/kunchenguid/no-mistakes.git \
  --fork-url https://github.com/myuser/no-mistakes-fork.git

Runtime Execution Flow

When you execute no-mistakes axi run, the pipeline automatically detects the fork configuration and splits the operations:


# Runtime behavior for configured forks:

# 1. Commit pushed to fork remote

# 2. PR opened against upstream with correct head reference

no-mistakes axi run

The tool invokes gh pr create with the --head parameter formatted as fork-owner:branch-name, ensuring GitHub correctly associates the PR with your fork while targeting the upstream base repository.

Summary

  • Dual URL storage: no-mistakes records upstream_url for PR bases and fork_url for push targets in the repository configuration.
  • GitHub-specific implementation: The NewWithFork constructor in internal/scm/github/github.go wires push operations to the fork while anchoring PRs to the upstream repository.
  • Automatic head construction: The fork owner extracted via github.RepoSlug populates the --head argument for GitHub CLI commands.
  • Provider limitations: GitLab, Bitbucket, and Azure DevOps explicitly disable fork PR routing and return implementation errors when fork_url is present.

Frequently Asked Questions

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

No. According to the source code in internal/pipeline/steps/host.go, no-mistakes explicitly disables fork PR routing for GitLab, Bitbucket, and Azure DevOps. When a fork_url is present for these providers, the pipeline returns an error message such as "fork PR routing for GitLab is not implemented" and skips PR creation.

What happens if I don't specify a fork_url during initialization?

If fork_url is omitted, the push and PR operations both target the upstream_url. The github.NewWithFork function receives an empty string for the fork parameter, causing the host to use the upstream remote for both pushing and pull request creation, effectively treating the repository as a direct clone rather than a fork workflow.

How does no-mistakes handle the --head argument for GitHub PRs?

The tool extracts the fork owner from fork_url using github.RepoSlug in internal/pipeline/steps/host.go. This owner string is passed to NewWithFork and subsequently used to construct the --head argument in the format <fork-owner>:<branch>. This ensures GitHub associates the pull request with the correct source fork while maintaining the upstream repository as the merge target.

Where is the fork configuration stored in no-mistakes?

The fork configuration persists in the repository record database, specifically in the fields upstream_url and fork_url as documented in AGENTS.md. These values are read during the push step in internal/pipeline/steps/host.go to determine whether to activate the fork-aware routing logic for GitHub repositories.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →