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 installas the first step in your project’sREADME.mdsetup guide and onboarding scripts. - In CI, always set
lfs: trueinactions/checkout(GitHub Actions) or addgit lfs pullexplicitly after checkout. - Use
git lfs cloneinstead ofgit clonefor 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
.lfsconfigto 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-filesmatches the expected count. - Use
git lfs trackto add.gitattributesrules 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.