# LFS (Large File Storage)

Copia repositories support Git LFS for storing large binary files. Workflows can fetch LFS-tracked files at checkout time by setting `lfs: true` on the `actions/checkout` step. However, a long-standing issue in the upstream `actions/checkout` action causes LFS object downloads to fail with **HTTP 400 Bad Request** against Copia servers. This page explains the cause and shows how to work around it for both `actions/checkout@v5` (and earlier) and `actions/checkout@v6`.

## Symptom

When a workflow runs `actions/checkout` with `lfs: true`, the Git fetch and the LFS batch metadata request succeed, but every LFS object download fails. The runner log shows two `Authorization` headers on the object request:

```
> GET /<owner>/<repo>.git/info/lfs/objects/<oid> HTTP/1.1
> Host: app.copia.io
> Authorization: Basic * * * * *
> Authorization: Basic * * * * *
< HTTP/2.0 400 Bad Request
```

## Cause

`actions/checkout` configures a host-wide `Authorization` header (`http.<server>/.extraheader` in Git config) so that subsequent `git` commands inherit credentials. This is fine for plain `git` operations.

When `git lfs` then talks to the LFS batch endpoint, the Copia server responds with **its own** per-object `Authorization` header — a short-lived token scoped to that object. `git lfs` adds that header to the object download request, but the inherited host-wide header is still in effect, so both end up on the same request. Cloudflare and Copia reject any request with duplicate `Authorization` headers.

The fix is to make sure the host-wide credential is not in effect when `git lfs` downloads the object. The mechanism differs between `actions/checkout@v5` (and earlier) and `actions/checkout@v6` because v6 changed where the credential is stored.

{% hint style="info" %}
In both versions, the workaround sets `lfs: false` on the `actions/checkout` step. This stops the action from running its own (failing) LFS fetch — you do the LFS fetch manually after adjusting credentials.
{% endhint %}

## Resolution for `actions/checkout@v5` and Earlier

In v5 and earlier, the credential lives directly in the repository's local Git config at `http.<server_url>/.extraheader`. The trick is to **move** it from the host-wide URL to a more specific URL that matches **only** the LFS batch endpoint. The batch request still gets the credential (so metadata works), but the object download URL no longer matches the prefix, so only the per-object header from the server is sent.

{% tabs %}
{% tab title="macOS / Linux (bash)" %}
{% code title=".copia/workflows/lfs-checkout-v5.yaml" %}

```yaml
steps:
  - uses: actions/checkout@v5
    with:
      lfs: false
      persist-credentials: true
  - name: Fetch LFS objects
    shell: bash
    run: |
      git lfs install --local
      SERVER="${{ github.server_url }}"
      REPO="${{ github.repository }}"
      AUTH=$(git config --local "http.${SERVER}/.extraheader")
      git config --local --unset "http.${SERVER}/.extraheader"
      git config --local "http.${SERVER}/${REPO}.git/info/lfs/objects/batch.extraheader" "$AUTH"
      git lfs pull
```

{% endcode %}
{% endtab %}

{% tab title="Windows (PowerShell)" %}
{% code title=".copia/workflows/lfs-checkout-v5.yaml" %}

```yaml
steps:
  - uses: actions/checkout@v5
    with:
      lfs: false
      persist-credentials: true
  - name: Fetch LFS objects
    shell: powershell
    run: |
      git lfs install --local
      $AUTH = git config --local "http.${{ github.server_url }}/.extraheader"
      git config --local --unset "http.${{ github.server_url }}/.extraheader"
      git config --local "http.${{ github.server_url }}/${{ github.repository }}.git/info/lfs/objects/batch.extraheader" "$AUTH"
      git lfs pull
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Resolution for `actions/checkout@v6`

In v6, the credential is no longer written to the local Git config. It is written to a separate file in `$RUNNER_TEMP` (named `git-credentials-<uuid>.config`) and pulled in via `includeIf.gitdir:` directives. As a result, the v5 workaround above is a silent no-op on v6 — there is no `http.<server>/.extraheader` entry in the local config to read or rewrite.

Instead, disable credential persistence in `actions/checkout` (so the credential file is removed before your script runs) and configure credentials yourself via the remote URL using a Personal Access Token.

{% tabs %}
{% tab title="macOS / Linux (bash)" %}
{% code title=".copia/workflows/lfs-checkout-v6.yaml" %}

```yaml
steps:
  - uses: actions/checkout@v6
    with:
      lfs: false
      persist-credentials: false
  - name: Fetch LFS objects
    shell: bash
    run: |
      SERVER="${{ github.server_url }}"
      HOST="${SERVER#https://}"
      git config --local lfs.url "https://x-access-token:${{ secrets.COPIA_TOKEN }}@${HOST}/${{ github.repository }}.git/info/lfs"
      git lfs install --local
      git lfs pull
```

{% endcode %}
{% endtab %}

{% tab title="Windows (PowerShell)" %}
{% code title=".copia/workflows/lfs-checkout-v6.yaml" %}

```yaml
steps:
  - uses: actions/checkout@v6
    with:
      lfs: false
      persist-credentials: false
  - name: Fetch LFS objects
    shell: powershell
    run: |
      $SERVER = "${{ github.server_url }}"
      $HOST = $SERVER -replace '^https?://', ''
      git config --local lfs.url "https://x-access-token:${{ secrets.COPIA_TOKEN }}@$HOST/${{ github.repository }}.git/info/lfs"
      git lfs install --local
      git lfs pull
```

{% endcode %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.copia.io/docs/actions/lfs-large-file-storage.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
