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

> Learn how no-mistakes handles GitHub PR creation using fork head references versus owner:branch format. It automatically adapts to GH CLI versions for seamless pull request management.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-07-04

---

**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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go) (lines 17–22) handles this logic:

```go
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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/pr.go) (lines 103–105), the resulting command follows this pattern:

```bash

# 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`](https://github.com/kunchenguid/no-mistakes/blob/main/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:

```go
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`](https://github.com/kunchenguid/no-mistakes/blob/main/github_test.go) (lines 73–80)—the system falls back to a plain branch query:

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

```

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

```go
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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github.go), containing the `headRef`, `FindPR`, and `matchesHead` functions. The orchestration layer in [`internal/pipeline/steps/pr.go`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/github/github_test.go), specifically verifying the owner-filtering logic when the CLI rejects qualified head references.