Git LFS Pointer Not Resolved Into the Real File

Your checked-out file contains LFS pointer text instead of the real binary. Re-pull the LFS objects and fix the smudge filter in three steps.

You cloned the repo and opened assets/hero-image.psd in Photoshop, only to see it crash immediately. Checking the file with file assets/hero-image.psd reveals it is not a PSD at all — it is an ASCII text file starting with version https://git-lfs.github.com/spec/v1. The Git LFS smudge filter should have swapped the pointer for the real binary on checkout, but it did not run. This happens when LFS is not installed on the machine, when the LFS remote is unreachable, or when the clone was performed in a way that skipped the smudge filter entirely. The fix is usually a single command, but diagnosing why the smudge filter did not run prevents the issue from recurring.

Common causes

Ordered by hit rate, highest first.

1. Git LFS is not installed on the machine

The repo was cloned before git lfs install was run. Without LFS installed, Git has no smudge filter registered for LFS pointers and checks out the raw pointer text.

How to spot it: git lfs version returns “command not found” or an older version than the repo requires. git config filter.lfs.smudge returns empty.

2. Clone was performed with --no-checkout or in a CI environment that skips filters

CI pipelines using actions/checkout with lfs: false (the default until recently) or git clone --no-checkout followed by a git checkout without LFS initialized will land on pointer text.

How to spot it: git lfs ls-files lists expected files but git lfs status shows them as “not pointers” (already overwritten) or points to zero-byte files.

3. LFS server is rate-limiting or returning 403 on the batch endpoint

On GitHub, hitting the LFS bandwidth limit returns a 403 Forbidden on the LFS batch API. The smudge filter silently falls back to writing the pointer text.

How to spot it: GIT_CURL_VERBOSE=1 git lfs pull 2>&1 | grep -E "403|429|rate limit" — any non-200 HTTP response from the LFS server.

4. LFS endpoint URL is wrong after a repo migration

The repo was migrated from GitHub to GitLab (or vice versa), but .lfsconfig or remote.origin.lfsurl still points to the old host. Downloads silently fail.

How to spot it: git lfs env — look for Endpoint= and verify it points to the correct host. git lfs push --dry-run will also surface a connection error.

5. Partial clone with --filter=blob:none skipped LFS blobs

When combining partial clone filtering with LFS, the blob filter can intercept LFS pointer blobs before the smudge filter runs, leaving them as pointers in the working tree.

How to spot it: git clone --filter=blob:none was used. git lfs pull fails with “object does not exist on the server” because the partial clone filter and LFS interact unexpectedly.

6. .gitattributes LFS track rule missing or wrong glob

The file was committed before the .gitattributes LFS track rule was added, so it is stored as a regular blob rather than an LFS object. The current .gitattributes has the rule, but the historical blob was never migrated.

How to spot it: git log --all --oneline -- .gitattributes | head -5 — if the track rule was added after the file was committed, old versions of the file are plain blobs.

Shortest path to fix

Step 1: Install and initialize Git LFS if missing

# macOS
brew install git-lfs
# or download from https://git-lfs.com

git lfs install   # registers the smudge/clean filters globally

Step 2: Pull all LFS objects for the current checkout

git lfs pull

This fetches all LFS objects referenced by the current commit and runs the smudge filter to replace pointer files with real content.

Step 3: Pull specific files if bandwidth is limited

git lfs pull --include="assets/hero-image.psd"
git lfs pull --include="assets/**"

Step 4: Fix the LFS endpoint URL after a migration

git lfs env   # shows current endpoint
git config lfs.url https://gitlab.example.com/org/repo.git/info/lfs
git lfs pull

Or update .lfsconfig in the repo root:

# .lfsconfig
[lfs]
  url = https://gitlab.example.com/org/repo.git/info/lfs

Step 5: Migrate historical plain blobs to LFS (for the wrong-gitattributes case)

git lfs migrate import --include="*.psd" --everything
git push --force-with-lease --all
git push --force-with-lease --tags

Notify all teammates to re-clone after migration.

Step 6: Verify real content was restored

file assets/hero-image.psd   # should return "Adobe Photoshop Image"
git lfs ls-files              # all tracked files should show "*" (checked out)

Prevention

  • Include git lfs install as the first step in your project’s README.md setup guide and onboarding scripts.
  • In CI, always set lfs: true in actions/checkout (GitHub Actions) or add git lfs pull explicitly after checkout.
  • Use git lfs clone instead of git clone for repos known to have large LFS objects — it runs the smudge filter more efficiently.
  • Monitor LFS bandwidth usage in GitHub/GitLab settings and set up alerts before hitting the quota to avoid 403 failures mid-sprint.
  • Add a .lfsconfig to the repo root that specifies the LFS URL explicitly, so it survives remote migrations.
  • After any repo migration, immediately verify LFS functionality: clone on a fresh machine and confirm git lfs ls-files matches the expected count.
  • Use git lfs track to add .gitattributes rules before committing any large file — never commit large binaries and then add the track rule later.

FAQ

Q: Can I check whether a file is an LFS pointer or a real file without opening it? A: Yes: git cat-file -p HEAD:assets/hero-image.psd | head -3. If the first line is version https://git-lfs.github.com/spec/v1, it is a pointer. If it starts with binary bytes or a known file header, it is a real blob.

Q: git lfs pull says “Object does not exist on the server.” The file is definitely tracked. What happened? A: The LFS object was likely never pushed. The committer added a pointer but never pushed the binary. They need to run git lfs push origin --all from their machine. If that is not possible, you need to recover the binary from another source.

Q: We exceeded GitHub LFS bandwidth. Can we use a different LFS server for the same repo? A: Yes. Set lfs.url in .lfsconfig to a self-hosted LFS server (e.g., MinIO with a Gitea LFS shim) and push the objects there. Teammates point their .lfsconfig to the new URL.

Q: Does git lfs work with shallow clones? A: Yes, but with caveats. git clone --depth 1 followed by git lfs pull works for the shallow commits, but older LFS objects referenced by deeper history are inaccessible until you unshallow.

Tags: #git #version-control #Troubleshooting