← Corpus / ai-labs / reminder
Never Run jj in a Git LFS Repo
jj does not run smudge/clean filters. In an LFS repo it commits the real bytes over the pointers — 282 MB in one save.
- Path
- reminders/Never-Run-JJ-In-A-Git-LFS-Repo.md
- Authors
- Michael Staton
- Augmented with
- Claude Code on Claude Opus 5 (1M context)
- Tags
- Reminder · Jujutsu · Git-LFS · Corpus · Client-Data
Never Run jj in a Git LFS Repo
Rule. Before running jj git init anywhere, check for Git LFS:
grep -l "filter=lfs" .gitattributes 2>/dev/null && echo "STOP — LFS repo, jj will corrupt it"
git lfs ls-files | head # non-empty = LFS in use
If the repo uses LFS, jj is not an option there. Not “use it carefully” — the two are mutually exclusive.
Why
jj’s own compatibility table says it outright
(studies/sync-and-content-version-control/jj/docs/git-compatibility.md:72):
Git LFS: No. (#80)
A grep for lfs or smudge across the whole jj source returns nothing. jj does
not run git’s clean/smudge filters at all. So when jj snapshots a working copy
in an LFS repo, it sees what LFS smudged onto disk — the real file — and stores
that, silently replacing the pointer.
What it actually did
jj git init --colocate in augment-it/clients/reach-edu/, whose
.gitattributes carries:
*.pdf filter=lfs diff=lfs merge=lfs -text
*.docx filter=lfs diff=lfs merge=lfs -text
One jj describe + jj new — a “save” that touched no files — produced a
commit reading:
78 updated · 282.5 MB
.../2026-06-10_ed559688-pdf.pdf | Bin 131 -> 729490 bytes
.../2025-2026-annual-report.pdf | Bin 133 -> 39846808 bytes
Bin 131 -> 729490 is the signature. A ~130-byte binary becoming megabytes
is an LFS pointer being overwritten with its own content. The pointer is three
lines:
version https://git-lfs.github.com/spec/v1
oid sha256:3471b398…
size 729490
Had that been pushed, the client’s repo would have gained 282 MB of blobs that LFS was specifically configured to keep out of it — and un-pushing it means a history rewrite on a client repo.
How to apply
- Check before you init. The grep above, every time, in any repo you did not personally create.
- The failure is silent and looks like success. jj reports “Initialized repo” and a normal-looking commit. Only the diffstat gives it away, and only if you read it. There is no warning.
git statuswill not save you. The working tree is clean before and after; LFS keeps real bytes on disk by design. The corruption is in what gets committed, not in what you see.- Recovery, if it already happened and nothing was pushed:
rm -rf .jj, thengit reset --hard <the commit you were on>, thengit checkout main— jj’s colocate moves the branch ref, so a reset alone leaves you detached. Confirm withgit show HEAD:<a.pdf> | head -1returning the LFS version line. - This does not touch the
.flavedecision. flave’s spec §8.2 chose jj for.flavebundles, and those carry no LFS. The rule is per repo, not per tool.
The wider lesson
The corpus repos use LFS because a corpus is markdown plus large binaries, and that is exactly the shape jj handles worst. Any future “adopt jj for the corpus” proposal has to answer this first, and the honest answer today is that it cannot: choosing jj means dropping LFS, and dropping LFS means the PDFs go into git proper.
Related
../../studies/sync-and-content-version-control/context-v/profiles/Profile__Jujutsu.md— the jj profile, which now records this../../flave-ai/context-v/specs/Master-Flave-An-Agent-Native-Document-Format-and-Publisher.md§8.2 — the jj decision this does not overturn../../../context-v/reminders/Check-The-Substrate-Before-Reasoning-On-Top-Of-It.md— the sibling guardrail; this is the same lesson applied to a tool instead of a store