# How actions/checkout Handles Pull Request HEAD Commit vs Merge Commit

> Understand how actions/checkout handles PR HEAD commits versus merge commits. Learn how the ref input determines which commit is fetched for your workflow.

- Repository: [GitHub Actions/checkout](https://github.com/actions/checkout)
- Tags: internals
- Published: 2026-07-16

---

**TLDR:** actions/checkout determines which commit to fetch by parsing the `ref` input, defaulting to `refs/pull/<number>/merge` for the merge commit, while explicitly providing `refs/pull/<number>/head` or a raw SHA triggers checkout of the PR head commit through specific fetch spec generation in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts).

The `actions/checkout` GitHub Action controls whether you receive the **merge commit** or the **head commit** of a pull request through its `ref` input parsing logic. By default, the action checks out the merge commit created by GitHub when running in a pull request workflow context, but you can override this to fetch the exact head commit of the PR branch. This behavior is governed by the ref normalization logic in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts) and the input handling in [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts).

## Default Behavior: The Merge Commit

When you use `actions/checkout` in a pull request workflow without specifying the `ref` input, the action automatically checks out the **merge commit**. This occurs because `github.ref` defaults to `refs/pull/<number>/merge` in pull request event contexts.

In [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts), the action reads the `ref` input (which defaults to `github.ref`) and passes it to the source provider. When this value contains `refs/pull/<number>/merge`, the `getCheckoutInfo` function in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts) preserves the branch suffix and constructs the remote tracking ref as `refs/remotes/pull/<number>/merge`.

## Ref Translation and Fetch Spec Construction

The core logic distinguishing head from merge commits resides in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts). The `getCheckoutInfo` function normalizes the input ref by checking its prefix:

```typescript
// refs/pull/
else if (upperRef.startsWith('REFS/PULL/')) {
    const branch = ref.substring('refs/pull/'.length)
    result.ref = `refs/remotes/pull/${branch}`
}

```

This normalization extracts the PR number and suffix (`merge` or `head`) and constructs a remote ref path. The `getRefSpec` function then generates the Git fetch specification that determines which object Git actually retrieves.

For a **merge commit**, the fetch spec becomes:

```text
+refs/pull/<number>/merge:refs/remotes/pull/<number>/merge

```

For a **head commit** (when `refs/pull/<number>/head` is provided), the spec becomes:

```text
+refs/pull/<number>/head:refs/remotes/pull/<number>/head

```

If you provide a raw SHA instead of a ref, `getRefSpec` adds a direct SHA-to-ref mapping like `+<sha>:refs/remotes/pull/<number>/head`, causing Git to fetch that specific commit object.

## Safety Validation for Fork Pull Requests

Before executing the fetch, [`src/unsafe-pr-checkout-helper.ts`](https://github.com/actions/checkout/blob/main/src/unsafe-pr-checkout-helper.ts) validates whether the checkout is safe. If you are checking out a pull request from a forked repository, the action throws an error unless you explicitly set `allow-unsafe-pr-checkout: true` in your workflow configuration. This security check prevents potentially malicious code from executing in your repository environment without explicit consent.

## Execution in the Git Source Provider

Finally, [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts) orchestrates the Git operations. It executes `git fetch` using the ref specification generated by [`ref-helper.ts`](https://github.com/actions/checkout/blob/main/ref-helper.ts), then checks out the resulting ref. Because the fetch spec maps the remote PR ref to a local remote-tracking branch, the checkout command receives the exact commit intended—whether that is the merge result or the head commit.

## Practical Configuration Examples

To check out the **merge commit** (default behavior):

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Show merge commit
        run: git rev-parse HEAD

```

To check out the **head commit** explicitly:

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Show head commit
        run: git rev-parse HEAD

```

Alternatively, you can use the ref syntax:

```yaml
- uses: actions/checkout@v4
  with:
    ref: refs/pull/${{ github.event.pull_request.number }}/head

```

## Summary

- **Default behavior**: actions/checkout fetches `refs/pull/<number>/merge` (the merge commit) when the `ref` input is omitted in PR workflows.
- **Head checkout**: Providing `refs/pull/<number>/head` or `${{ github.event.pull_request.head.sha }}` forces checkout of the PR head commit.
- **Core logic**: [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts) contains `getCheckoutInfo` and `getRefSpec` functions that normalize refs and build Git fetch specifications.
- **Security**: [`src/unsafe-pr-checkout-helper.ts`](https://github.com/actions/checkout/blob/main/src/unsafe-pr-checkout-helper.ts) requires explicit opt-in (`allow-unsafe-pr-checkout: true`) when checking out forks.
- **Execution**: [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts) performs the actual `git fetch` and checkout using the generated specifications.

## Frequently Asked Questions

### What is the difference between refs/pull/<number>/merge and refs/pull/<number>/head?

`refs/pull/<number>/merge` is a synthetic ref created by GitHub that represents the result of merging the PR branch into the target branch, while `refs/pull/<number>/head` points directly to the tip commit of the pull request branch. The merge commit allows you to test the integration result, whereas the head commit represents the exact code submitted by the author.

### How do I configure actions/checkout to use the head commit instead of the merge commit?

Set the `ref` input to either `${{ github.event.pull_request.head.sha }}` or `refs/pull/${{ github.event.pull_request.number }}/head`. According to the logic in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts), this bypasses the default merge ref mapping and generates a fetch spec that retrieves the head commit instead of the merge commit.

### What is the allow-unsafe-pr-checkout flag and when do I need it?

The `allow-unsafe-pr-checkout` input, declared in [`action.yml`](https://github.com/actions/checkout/blob/main/action.yml) and enforced by [`src/unsafe-pr-checkout-helper.ts`](https://github.com/actions/checkout/blob/main/src/unsafe-pr-checkout-helper.ts), is required when checking out pull requests from forked repositories. This flag prevents automatic execution of untrusted code from forks by requiring explicit opt-in to the security risk.

### Which source files handle the ref parsing logic?

The primary ref parsing occurs in [`src/ref-helper.ts`](https://github.com/actions/checkout/blob/main/src/ref-helper.ts), specifically in the `getCheckoutInfo` function which normalizes PR refs, and `getRefSpec` which builds the fetch arguments. Input acquisition happens in [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts), while [`src/git-source-provider.ts`](https://github.com/actions/checkout/blob/main/src/git-source-provider.ts) executes the Git commands using these specifications.