Guía9 min

git difftool para coding agents: GUI humana, no el diff

Resumen

git difftool es un frontend de git diff que abre vim, FileMerge o VS Code. Sin diff.tool avisa y prueba fallbacks. Cero -y/--gui/--dir-diff autónomo. Distinto de diff, diff-files, diff-index y mergetool. Git 2.50.1.

GitHub
Un coding agent lee git diff --stat; la GUI de FileMerge queda al humano

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 difftool abre un programa gráfico o un editor para ver el mismo conjunto que diff. El man (git-difftool(1); git-scm.com/docs/git-difftool 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): Show changes using common diff tools. DESCRIPTION: frontend to git diff and accepts the same options and arguments. SYNOPSIS: git difftool [<options>] [<commit> [<commit>]] [--] [<path>...].

Contrato para un coding agent: no lo lances. Es GUI (opendiff, vscode, meld) o un Vim interactivo. Necesita TTY y, casi siempre, DISPLAY. Ante un diff, usa --stat, --name-only o --quiet --exit-code. No -y. No --gui. No --dir-diff. No --extcmd que vuelque $LOCAL/$REMOTE al contexto. No diff.tool global.

No es diff: diff imprime texto. Aquí el texto no llega: llega FileMerge. No es diff-files ni diff-index ni diff-tree: esos son plumbing raw. No es git mergetool: mergetool corre después de un merge con UU y escribe MERGED. difftool no resuelve conflictos.

Qué hace (y qué no)

Sin cambios, sale 0 y no imprime nada. Verificado 2026-09-06 en un worktree limpio: git difftool → vacío, exit 0. --tool-help lista tools y sale 0. En este Mac: disponibles opendiff, vimdiff/vimdiff1-3, vscode. El resto (meld, kdiff3, nvimdiff, gvimdiff, bc4…) aparece como valid, but not currently available. El pie: Some of the tools listed above only work in a windowed environment. --tool-help es diagnóstico, no una invitación a elegir tool y dispararlo.

Sin diff.tool, avisa y prueba fallbacks. Verificado en un archivo dirty, stdin cerrado: This message is displayed because 'diff.tool' is not configured. Luego: 'git difftool' will now attempt to use one of the following tools: opendiff kompare emerge vimdiff nvimdiff. Prompt: Viewing (1/1): 'f.txt' / Launch 'opendiff' [Y/n]?. Exit 0 si no contestas (EOF). No asumas que “no configurado” = no-op.

--tool=notatool con cambios reales falla. Verificado dirty + -y: error: difftool.notatool.cmd not set for tool 'notatool', fatal: external diff died, stopping at f.txt, exit 128. El mismo tool sin -y sobre dos commits ya hechos pregunta Launch 'notatool' [Y/n]? y con EOF sale 0: Git ni valida el cmd hasta que alguien dice que sí. --dir-diff --tool=notatool → el mismo error de cmd, warning: failed: 1, exit 1. Códigos distintos, mismo verbo prohibido.

Dos paneles LOCAL y REMOTE; el agente no abre la GUI

GitHub Docs (/en/pull-requests/how-tos/review-pull-requests/reviewing-proposed-changes-in-a-pull-request, HTTP 200; la URL vieja …/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/… 301). El review de un PR es el diff en la web, no FileMerge en tu Mac. Un agente que “revisa” abriendo VS Code no deja comentario en el PR.

Flags que un agente no toca

Flag / configManAgente
-t / --tool=<tool>Usa ese programa. Built-ins: emerge, kompare, meld, vimdiff…Cero. Abre GUI o vim
--tool-helpLista tools válidosSí, solo lectura
-y / --no-promptNo pregunta antes de cada archivoCero autónomo. En este Mac lanza FileMerge
--promptPregunta para poder saltar el path. Default; pisa -yCero
-g / --guiLee diff.guitool en vez de diff.toolCero. Quiere DISPLAY
--no-guiFuerza diff.toolCero
-d / --dir-diffCopia a un temp y hace un diff de directorio. never promptsCero. Árboles temporales
-x / --extcmd=<command>Ignora defaults; corre <command> $LOCAL $REMOTECero. El payload va al stdout
--trust-exit-codePropaga el exit del toolCero. Default ignora errores del tool
--[no-]symlinksEn --dir-diff, symlink al worktree o copia. Default copies en WindowsCero autónomo
--rotate-to / --skip-toReordena o salta pathsCero. Path ausente → 128

Verificado 2026-09-06:

  • -y --extcmd con printf: $LOCAL es un blob temporal; $REMOTE en un dirty es el path del worktree. Dos commits: ambos temporales. Exit 0.
  • --dir-diff -y --extcmd /bin/echo: imprime …/git-difftool.*/left/ y …/right/. Exit 0. Un printf con espacios no es un ejecutable: cannot run … No such file or directory, warning: failed: -1, exit 1.
  • -y --trust-exit-code --extcmd falsefatal: external diff died, stopping at f.txt, 128. El mismo --extcmd false sin trust → 0. El default traga el fallo del tool.
  • --gui + --extcmd juntos: options '--gui' and '--extcmd' cannot be used together, 128.
  • -y --prompt --extcmd /bin/echo sigue preguntando. -y no gana.
  • --no-index -y --extcmd entre dos archivos distintos: imprime $LOCAL $REMOTE y sale 1 (el 1 es de diff --no-index cuando hay diferencias, no un crash).
  • --rotate-to / --skip-to con un path que no está en el diff: fatal: No such path 'g.txt' in the diff, 128.
  • HEAD HEAD: 0, vacío.

Custom tool: difftool.<tool>.cmd corre en shell con LOCAL (pre-image temporal) y REMOTE (post-image). $MERGED y $BASE existen por compatibilidad con mergetool y valen lo mismo que $MERGED = archivo comparado. Un cmd que cat esos temporales filtra el parche al LLM. Prohibido.

Config que no pisa un agente

El man incluye el bloque de git-config(1) (git-scm.com/docs/git-config HTTP 200, last-modified 2026-08-31). difftool cae a las variables de mergetool si las suyas no existen.

  • diff.tool / diff.guitool: cuál programa. overrides the value configured in merge.tool / merge.guitool. Un agente no hace git config --global diff.tool. Eso es config local, y global queda fuera.
  • difftool.<tool>.path / .cmd: path o comando custom.
  • difftool.trustExitCode: equivalente a --trust-exit-code. Default ignora.
  • difftool.prompt: prompt por archivo.
  • difftool.guiDefault: true--gui; auto mira DISPLAY. Default false: hace falta --gui explícito.

vimdiff espera un humano que salga con :qa. Un agente no abre Vim para “ver el diff”.

Flujo seguro

  1. status porcelain. ¿Hay cambios?
  2. Pregunta binaria: git diff --quiet --exit-code. 0 = limpio, 1 = hay diff. Hay diff ≠ error de comando.
  3. Lista: git diff --name-only / --name-status + pathspec. Resumen: --stat. Cero -p al LLM.
  4. No git difftool. No -y. No --dir-diff. No --extcmd cat.
  5. Review de PR: el diff de GitHub, no FileMerge. Un comentario en el PR no nace de $LOCAL.
  6. Si el humano pide GUI, él lanza difftool. Tú reportas paths.

El agente usa --stat; FileMerge queda fuera del loop

Checklist:

  • diff.tool no lo seteas tú.
  • Cero -y, --gui, --dir-diff, --tool=vimdiff autónomo.
  • Cero --extcmd que imprima LOCAL/REMOTE.
  • --tool-help sí; lanzar el tool no.
  • Conflicto UU = mergetool del humano o abortar. difftool no lo cierra.
  • El 1 de --no-index o de --quiet --exit-code no es un crash.

FAQ

¿Puedo usar difftool en CI? No. No hay DISPLAY ni humano. CI usa git diff --exit-code y deja el job rojo.

¿Es lo mismo que el diff de VS Code / Cursor? El tool vscode de --tool-help requires a graphical session. El agente CLI no es esa sesión. No lo dispares con -y.

¿Y si el humano ya tiene diff.tool=opendiff? Sigue sin lanzarlo. FileMerge espera un clic. Lo verificado aquí: -y sin tool configurado llega a FileMerge y no vuelve. Tu trabajo es --stat y parar.

¿--dir-diff es más seguro porque no pregunta? Al revés. El man: This mode never prompts before launching the diff tool. Copia el árbol a un temp y abre el GUI de golpe.

¿mergetool y difftool son el mismo binario? No. git-mergetool(1) (HTTP 200, last-modified 2026-08-31): Run merge conflict resolution tools. difftool es frontend de diff. Ver el hub de comparativas si estás eligiendo verbo.

¿--extcmd sirve para un “diff seguro”? No para el agente. $LOCAL es un archivo temporal con el contenido completo. Imprimirlo es volcar el parche. Usa git diff --stat.

Si estás armando el agente desde cero, el curso de instalar un agente cubre el loop y las tools. Esta guía es la regla cuando el diff ya existe: no lo abras en una GUI.