How no-mistakes Handles GitHub PR Creation: Fork Head References vs. Owner:Branch Format

no-mistakes automatically detects repository forks and adapts its GitHub CLI invocations to use either the owner:branch syntax or plain branch names, ensuring pull requests are created and found correctly across different gh CLI versions.

The no-mistakes repository provides a robust pipeline system for automating GitHub operations. When handling no-mistakes GitHub PR creation, the tool must distinguish between working on a direct repository clone versus a fork, as each requires different reference formats when interacting with the GitHub CLI. This detection happens in the GitHub host implementation located in internal/scm/github/github.go.

Detecting Forks and Building Head References

The GitHub host stores fork information in the forkOwner field when a fork URL is supplied during initialization. This value determines how the tool constructs head references for all subsequent PR operations.

The headRef method in internal/scm/github/github.go (lines 17–22) handles this logic:

func (h *Host) headRef(branch string) string {
    if h.forkOwner == "" {
        return branch                // normal repo
    }
    return h.forkOwner + ":" + branch // fork repo
}

When forkOwner is empty, the method returns the plain branch name. For forked repositories, it returns the owner:branch format (e.g., fork-owner:feature-branch) that GitHub requires to identify the correct head reference across repository boundaries.

Creating Pull Requests from Forks

When the PRStep orchestrator invokes host.CreatePR, it builds the head argument using h.headRef(branch). According to the implementation in internal/pipeline/steps/pr.go (lines 103–105), the resulting command follows this pattern:


# For forks:

gh pr create --head fork-owner:feature-branch --base main ...

# For non-forks:

gh pr create --head feature-branch --base main ...

This ensures that when creating a PR from a fork, the GitHub CLI receives the fully qualified reference that points to the forked repository rather than attempting to locate the branch in the upstream repository.

Locating Existing PRs with Dual Strategy

Finding existing PRs requires a more sophisticated approach due to varying support for the owner:branch syntax across GitHub CLI versions. The FindPR method in internal/scm/github/github.go implements a two-phase strategy.

Phase 1: Owner-Qualified Syntax (Modern gh CLI)

First, the code attempts to use the preferred owner:branch syntax. The argument construction in lines 58–68 includes:

args := []string{"pr", "list", "--head", branch}
if base != "" { args = append(args, "--base", base) }
args = append(args, h.repoArgs()...)
jsonFields := "number,url"
if h.forkOwner != "" {
    jsonFields = "number,url,headRefName,headRepositoryOwner"
}
args = append(args, "--state", "open", "--json", jsonFields)

When a fork is detected, the code requests additional JSON fields (headRefName and headRepositoryOwner) to enable post-filtering if the initial command fails.

Phase 2: Fallback with Server-Side Filtering

If the first attempt fails—specifically when older gh versions reject the <owner>:<branch> format as documented in github_test.go (lines 73–80)—the system falls back to a plain branch query:

gh pr list --head feature-branch ...

After receiving the response, the matchesHead function (lines 203–215) validates each candidate PR:

func (h *Host) matchesHead(headRefName string, owner *struct{Login string}, branch string) bool {
    if h.forkOwner == "" { return true }
    if strings.TrimSpace(headRefName) != "" && headRefName != branch { return false }
    if owner == nil { return false }
    return strings.EqualFold(strings.TrimSpace(owner.Login), h.forkOwner)
}

This filtering ensures that even when querying without the owner prefix, only PRs whose headRepositoryOwner.login matches the recorded forkOwner are accepted.

Version Compatibility: The Fallback Mechanism

The fallback strategy ensures no-mistakes GitHub PR creation works reliably across GitHub CLI versions. Older versions of gh return an error when encountering the <owner>:<branch> syntax in the --head flag:


invalid argument: "--head" does not support "<owner>:<branch>"

The test case TestFindPRForkUsesBareHeadAndFiltersOwner in github_test.go verifies this behavior. When the owner-qualified command fails, the system:

  1. Retries with the plain branch name
  2. Requests additional JSON metadata for fork filtering
  3. Applies the matchesHead validation to discard PRs from other owners

This approach prevents accidental cross-owner PR matches while maintaining compatibility with legacy GitHub CLI installations.

Summary

  • Fork Detection: The Host struct stores forkOwner when initialized with a fork URL, enabling conditional logic throughout the PR lifecycle.
  • Head Reference Construction: The headRef method in github.go returns owner:branch for forks and plain branch for direct repositories.
  • Creation Flow: PRStep uses headRef to build the --head argument for gh pr create, ensuring correct targeting of forked branches.
  • Discovery Strategy: FindPR attempts the modern owner:branch syntax first, then falls back to plain branch names with client-side filtering via matchesHead.
  • Cross-Version Support: The dual-strategy approach ensures compatibility with both newer and older GitHub CLI versions while preventing cross-repository PR confusion.

Frequently Asked Questions

How does no-mistakes detect if a repository is a fork?

The detection occurs during host initialization in internal/scm/github/github.go. When a fork URL is supplied, the system extracts and stores the fork owner in the forkOwner field. This value persists throughout the session and drives the conditional logic in headRef and matchesHead methods.

Why does no-mistakes use two different strategies for finding PRs?

The dual-strategy approach addresses version fragmentation in the GitHub CLI. Newer versions support the owner:branch syntax directly in the --head flag, while older versions reject it. By attempting the modern syntax first and falling back to plain branch names with client-side filtering, the tool maintains compatibility across all gh CLI versions without requiring version detection logic.

What happens if the GitHub CLI version doesn't support owner:branch syntax?

When the initial gh pr list --head owner:branch command fails, the system automatically retries with gh pr list --head branch. It then inspects the JSON response fields headRefName and headRepositoryOwner to filter the results, ensuring only PRs from the correct fork owner are returned. This validation happens in the matchesHead function.

Which source files contain the fork handling logic?

The primary implementation resides in internal/scm/github/github.go, containing the headRef, FindPR, and matchesHead functions. The orchestration layer in internal/pipeline/steps/pr.go invokes these methods during PR creation and updates. Test coverage for the fallback behavior is located in internal/scm/github/github_test.go, specifically verifying the owner-filtering logic when the CLI rejects qualified head references.

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 →