git diff-files para coding agents: working tree vs index, no el parche
Resumen
git diff-files compara el working tree con el index en raw. Default exit 0 aunque haya sucio; --quiet implica --exit-code. -q no es silencio: oculta paths inexistentes. Cero dump al LLM. Distinto de git diff, status y diff-index. Git 2.50.1.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
git diff-files compara el working tree con el index. El man (git-diff-files(1); git-scm.com/docs/git-diff-files HTTP 200, last-modified 2026-08-31; pie del man local Git 2.50.1.428.g0e8243, 2025-07-22; binario Git 2.50.1 / Apple Git-155): Compares the files in the working tree and the index. DESCRIPTION: When paths are specified, compares only those named paths. Otherwise all entries in the index are compared. El formato default es el mismo raw que git-diff-index y git-diff-tree.
No es git diff: ese porcelain elige dos extremos y, sin flags, imprime un parche unificado para humanos. No es status: status también lista untracked e index vs HEAD. No es git diff-index: ese verbo pide un <tree-ish> y compara un árbol con el working tree o, con --cached, con el index. diff-files no toma tree-ish. Solo index ↔ disco.
Contrato: no vuelques git diff-files -p al LLM. Inspección = --name-only / --name-status / --quiet. Restaurar = restore. Stage = add con paths. Cero -p autónomo. Cero dump raw de mil paths.
Qué hace (y qué no)
Lista de paths del index → blob del index vs archivo en disco. Untracked no aparece. HEAD no entra. Si el path está staged y el disco coincide con el index, la salida está vacía.
Verificado 2026-09-06 (Git 2.50.1 / Apple Git-155), repo mínimo con a.txt commiteado:
| Caso | Comando | Salida | Exit |
|---|---|---|---|
| Working tree limpio | git diff-files | silencio | 0 |
| Disco sucio vs index | git diff-files | raw :100644 100644 <src> 0{40} M\ta.txt | 0 |
| Mismo sucio | git diff-files --name-only | a.txt | 0 |
| Mismo sucio | git diff-files --quiet | silencio | 1 |
| Mismo sucio | git diff-files --exit-code | el raw | 1 |
| Mismo sucio | git diff-files -q | el raw | 0 |
Path inexistente sin -- | git diff-files missing.txt | fatal: ambiguous argument | 128 |
Path inexistente con -- | git diff-files -- missing.txt | silencio | 0 |
Tras git add (index = disco) | git diff-files | silencio | 0 |
| Archivo borrado del disco | git diff-files --name-status | D\ta.txt | 0 |
Untracked b.txt | git diff-files --name-only | no lista b.txt | 0 |
El SHA de destino es todo ceros cuando el working tree no coincide con el index. El man (RAW OUTPUT FORMAT): The sha1 for "dst" is shown as all 0's if a file on the filesystem is out of sync with the index. Verificado: 0000000000000000000000000000000000000000 en la columna dst.
-q no es --quiet. El man: -q — Remain silent even for nonexistent files. En el lab, -q imprimió el raw del path sucio y salió 0. --quiet Disable all output of the program. Implies --exit-code. Ese sí es el gate de scripts: 0 = limpio, 1 = hay diff.
--exit-code: Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and 0 means no differences. Sigue imprimiendo. Si quieres solo el código, --quiet.

Lo que el agente sí / no corre
| Quiero | Comando | Trampa |
|---|---|---|
| ¿Hay sucio vs index? | git diff-files --quiet | git diff-files a pelo sale 0 con sucio |
| Lista de paths sucios | git diff-files --name-only | -p es el parche entero |
| Status humano | status -sb | diff-files no ve untracked ni staged vs HEAD |
| Diff staged | git diff --cached --name-only | diff-files deja de ver lo que ya está en el index |
| Parche para review | git diff --stat | -p de plumbing al contexto |
Prohibido en autónomo:
-p/--patch/-U/--statsin techo. El man genera parche unificado. Un agente no se lo traga.- Confundir
-qcon--quiet.-qno silencia diffs reales; solo paths inexistentes. - Tratar exit 0 del default como “working tree limpio”. El default no implica
--exit-code. - Path sin
--si puede no existir: 128, unknown revision or path not in the working tree. - Usarlo como “qué cambió el PR”. Eso es
git diff A...Bo el three-dot de GitHub, no este verbo. - Encadenarlo con checkout-index
-f“para alinear”. Detectar sucio ≠ pisar.
El porcelain de diff ya cubre “muéstrame el unstaged”. Este verbo existe para scripts que necesitan raw estable: The output format is the same as for git diff-index and git diff-tree. Repeatability, no lectura humana.
Receta (60 segundos)
Solo en un worktree propio:
git status -sb
git diff-files --quiet
git diff-files --name-only
Tres líneas. status te da el panorama (incluye untracked). --quiet es el semáforo: 0 sigues, 1 hay desvío index↔disco. --name-only lista qué paths, no el parche.
Si --quiet es 1 y el ticket pide commitear, el siguiente paso es add de paths explícitos, no git add .. Si pide descartar, restore -- <path>.
Para un path concreto:
git diff-files --quiet -- src/app.ts
echo $?
-- antes del path. Verificado: sin --, un filename que no está en el árbol revienta con 128; con --, el path ausente es no-op 0.
Trampas que ya vimos
- “Exit 0 = limpio.” Solo con
--quieto--exit-code. Default sucio = 0 + una línea raw. - “
-qes quiet.” No.-q= no grites si el archivo no existe. El sucio real se imprime igual. En el lab,-qy el default fueron idénticos. - “Es lo mismo que
git diff.”git diffsin args también es WT vs index, pero en formato parche y con color/pager. diff-files default es raw de una línea por path. Distinto contrato, distinto volumen. - “También ve staged.” Tras
git add, diff-files se queda mudo: el index es el disco. Lo staged vive engit diff --cached. - “También ve untracked.” No. El man compara entries in the index.
b.txtnuevo no está en el index; no existe para este verbo. status--porcelain=v1 -zsí lo lista. - Path sin
--. 128. El mensaje habla de revision. No es que Git “no encuentre el diff”; es que parseó el token como rev. - dst SHA a ceros = blob vacío. No. Ceros = work tree out of sync with the index. El contenido sucio está en disco, no en un blob.
-1/-2/-3en un merge. El man: diff contra base / ours / theirs; default-2(ours) y paths limpios.-0omite unmerged y solo dice Unmerged. Un agente en conflicto lee status, no improvisa stages.- Creer que mueve HEAD o el index. Solo lee. El inverso de escribir al index es update-index; al disco, checkout-index o restore.
Checklist
- Worktree propio, no el checkout del humano.
-
git status -sbleído antes. - Quiero index ↔ disco, no HEAD, no untracked, no un PR.
- Gate =
--quiet(exit 0/1). Cero asumir el default. - Lista =
--name-onlyo--name-status. Cero-p. - Paths con
--. Cero filenames sueltos que puedan 128. -
-q≠--quiet. No los intercambies. - Si hay que restaurar: restore, no force.
- Si hay que stagear: add de paths, no
..
FAQ
¿diff-files mira HEAD? No. Solo index y working tree. HEAD entra con git diff HEAD o git diff-index HEAD.
¿Por qué el default no sale 1? Plumbing histórico: el raw se parsea; el código de salida no era el contrato. --exit-code / --quiet existen precisamente para scripts que sí quieren diff(1).
¿Cuándo sí -p? Cuando un humano pide el parche de un path en un worktree propio. Nunca como primer vistazo ni sobre el árbol entero.
¿Y untracked? status --porcelain=v1 -z, o git ls-files -o. Este verbo no los ve.
¿Dónde encaja esto? En el hub de comparativas y decisiones, junto a git diff y status. Si estás armando el primer agente, empieza por el curso.

Lecturas relacionadas
Sigue explorando Coding Agents y otras piezas para builders.

git stripspace para coding agents: limpia el mensaje, no el parche

git interpret-trailers para coding agents: parse, no reescribir el mensaje

git patch-id para coding agents: huella del diff, no del SHA
