lazy documentationv0.90

The git LFS guard

Lazy refuses to launch an agent into an environment where git LFS would silently store raw file content, and refuses to accept a branch that already contains such a commit. This document explains the defect, the two checks, and what to do when either fires.

The defect

A repository tracks large files with git LFS: a checked-in .gitattributes carries filter=lfs for some path, and git add is supposed to run the LFS clean filter so the commit holds a ~130-byte pointer instead of the file.

Whether that happens depends on the git config of the clone doing the commit — filter.lfs.process / clean / smudge, written by git lfs install. If those are missing or empty, git skips the filter. Whether that is an error depends on one more key:

filter.lfs.required broken filter behaves as
true fatal: <path>: clean filter 'lfs' failed — the add fails
false (or unset) the raw file is committed, exit 0, no output

The second row is the whole problem. Nothing errors, nothing warns, and the commit looks normal until a push hits the forge's blob-size limit.

For example, a clone with filter.lfs.process set but empty and required = false will commit a 335 MB dataset file verbatim where a 134-byte pointer belongs. The task branch becomes unpushable, and recovery takes manual history surgery.

Layer 1: the start-time preflight

When a task starts on a repository that uses LFS, lazy inspects the environment the agent will commit from — git-lfs on PATH, the three filter hooks non-empty, filter.lfs.required true — before the worktree is created. If anything is wrong the start is refused with one line and a pointer to lazy doctor:

Refusing to start task t1: This repository uses git LFS, but the LFS clean/smudge
filter is not configured here (filter.lfs.process unset or empty); and
`filter.lfs.required` is false, so git commits raw file contents instead of
failing when the filter is broken. Commits made here would silently store raw
file content instead of LFS pointers, producing a branch that cannot be pushed.

Run `lazy doctor` for details.

lazy doctor carries the full diagnosis and the exact commands — that split is deliberate: the point of occurrence gets one generic line, doctor is the single place remedies live.

Lazy never repairs the config for you. Writing to a user's git config as a side effect of lazy start is a hidden side effect lazy avoids by design. Lazy detects and refuses; you fix it deliberately:

git lfs install --local          # writes filter.lfs.process/clean/smudge + required
git config filter.lfs.required true

Configure the mode with [git] lfs_check in lazy.toml:

lfs_check does not affect the accept-time guard below, which is always on.

Layer 2: the accept-time guard

Before lazy accept merges anything, lazy scans merge-base..<task branch> for files on LFS-tracked paths whose committed blob is not a pointer, and refuses if it finds any:

Accepting task t1 would merge `lazy/add-dataset` into `main` with 1 file stored
as RAW CONTENT on an LFS-tracked path:

  datasets/train.bin  (335 MB of raw content)
      committed by 4f2a1c9e "Add training data" (2026-08-09)
…

The message names each file with its size and the commit that introduced it, explains why nothing failed at commit time, and gives the recovery route:

  1. fix the environment first (git lfs install --local && git config filter.lfs.required true) — otherwise the re-commit repeats the mistake;
  2. re-stage the paths through the filter (git rm --cached … && git add …) and commit, confirming with git cat-file -s <branch>:<path> that the blob is now pointer-sized;
  3. if the branch's history still carries the raw blob the push will still fail — redo the work on a fresh branch (lazy redo <task>) rather than rewriting shared history in place.

If a file genuinely belongs in git as-is, approve it explicitly:

lazy accept t1 --approve-file datasets/train.bin

(or approved_files over MCP). Each file must be approved individually; the accept then proceeds and records a warning naming what was approved, so the audit trail shows a human made the call. This reuses the same approval channel as the resurrection guard rather than inventing a second one.

Lazy does not rewrite bad commits for you. Repairing history on a branch is the human's call.

How detection works

Both layers read git data only — no git-lfs binary is required to detect LFS usage or to find raw blobs. The binary is consulted for exactly one question ("is it installed?"), so a machine without git-lfs is still fully diagnosable.

Scope and limits

The accept-time check runs in the daemon, and the launch preflight runs in the daemon's task launcher, so both runners and every caller are covered.