Guía9 min

git write-tree para coding agents: tree del index, no un commit

Resumen

git write-tree imprime el SHA de un tree a partir del index. No mueve HEAD. El index tiene que estar merged. Un agente no encadena commit-tree, no usa --missing-ok, no fabrica trees por REST. Distinto de hash-object, ls-tree y commit. Git 2.50.1.

GitHub
El index se serializa a un tree object; HEAD y el working tree no se mueven

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 write-tree no crea un commit. El man (git-write-tree(1); git-scm.com/docs/git-write-tree HTTP 200, last-modified 2026-08-31; last updated in 2.50.0, 2.50.1 → 2.55.0 sin cambios; pie del man local Git 2.50.1.428.g0e8243, 2025-07-22; binario Git 2.50.1 / Apple Git-155): Create a tree object from the current index. SYNOPSIS: git write-tree [--missing-ok] [--prefix=<prefix>/]. Imprime el SHA del tree a stdout. HEAD, working tree y refs no se mueven.

El index tiene que estar fully merged. El man: The index must be in a fully merged state. Conceptualmente sync()s the current index contents into a set of tree files. Para que ese tree coincida con el directorio de ahora, hace falta un git update-index (o add) antes. write-tree no lee el working tree.

GitHub Docs (REST API endpoints for Git trees, HTTP 200 2026-09-06, API version 2026-03-10): Create a tree pide Contents write y responde 201 con sha. Get a tree pide Contents read. Ninguno mueve una rama. Un agente de lectura no llama el POST.

Contrato: inspeccionar el SHA del index, cero --missing-ok, cero --prefix autónomo, cero commit-tree, cero REST de escritura.

Qué hace (y qué no)

Crea un objeto tree a partir del index actual. El mismo index, el mismo SHA. Verificado 2026-09-06 (Git 2.50.1 / Apple Git-155): echo hello > a.txt && git add a.txt && git write-tree dos veces seguidas → 2e81171448eb9f2ee3821e3d447aa6b2fe3ddba1. cat-file -ttree, -s33. ls-tree lista 100644 blob ce013625… a.txt. git status -sb sigue en A a.txt. HEAD sigue inexistente si no hay commits.

No es hash-object. hash-object hashea un blob. write-tree hashea el directorio del index (blobs + sub-trees). El SHA del file no es el SHA del tree.

No es ls-tree. ls-tree lee un tree-ish. write-tree escribe un tree nuevo (o reusa el que ya existía) y te da el SHA.

No es commit. commit crea un objeto commit, mueve la rama, pide mensaje. write-tree se queda en el object store. El plumbing siguiente es git commit-tree <tree> — el man de commit-tree dice This is usually not what an end user wants to run directly. Un agente no lo encadena.

Ni el working tree. Verificado: editar a.txt sin add y volver a git write-tree imprime el SHA viejo. Tras git add a.txt, el SHA cambia (5fb61ad4…). write-tree no “fotografía el disco”.

write-tree serializa el index; un edit sucio no cambia el SHA

Lo que el agente sí / no corre

QuieroComandoTrampa
SHA del index ahoragit write-treeasumir que es un commit o que HEAD se movió
¿Coincide con HEAD?write-tree vs rev-parse HEAD^{tree}comparar contra HEAD a pelo: eso es commit, no tree
Ver qué hay dentrols-tree <sha>pegar el tree al LLM
Index vacíogit write-treeinventar un SHA: el empty tree es 4b825dc642cb6eb9a060e54bf8d69288fbee4904
Conflictostatus -sbwrite-tree “para ver si pasa”

Prohibido en autónomo:

  • Encadenar a git commit-tree. El man de commit-tree: Creates a new commit object based on the provided tree object and emits the new commit object id on stdout. No actualiza HEAD. El siguiente paso típico es git update-ref. Eso es fabricar historia por plumbing. Si el ticket pide un commit, el camino es add + commit -m, en un worktree propio.
  • --missing-ok. El man: Normally git write-tree ensures that the objects referenced by the directory exist in the object database. This option disables this check. Un tree que apunta a blobs que no están es un objeto podrido. Un agente no lo pide.
  • --prefix=<prefix>/ autónomo. El man: Writes a tree object that represents a subdirectory <prefix>. Verificado: --prefix=nope/fatal: git-write-tree: prefix nope/ not found, 128. Sirve para subproyectos; no para “saca el tree de src/”.
  • Create a tree (POST /repos/{owner}/{repo}/git/trees). GitHub Docs: Contents write, tree array obligatorio, 201. No mueve la rama. Un agente de lectura no lo llama.
  • Correrlo sobre un index con conflictos “a ver”. Verificado 2026-09-06: merge con UU a.txt → tres líneas a.txt: unmerged (<sha>) (stages 1/2/3) y fatal: git-write-tree: error building trees, 128. El contrato del man se cumple: fully merged o nada.
  • Meter el SHA en un prompt largo. Un tree SHA no es un diff. Si necesitas paths, ls-tree -z con pathspec.

-h: usage, 129. No hay -n dry-run. No hay pathspec. No hay --stdin.

Receta (60 segundos)

Solo en un worktree propio:

git status -sb
git diff --cached --stat
git write-tree

Tres líneas. status te dice si hay UU (entonces para: write-tree va a 128). --stat del cached te dice qué hay en el index. write-tree imprime 40 hex.

Para comparar contra HEAD después de un commit:

git rev-parse HEAD^{tree}
git write-tree

Si coinciden, el index es exactamente el tree del HEAD. Si no, hay staged changes. Eso no es “hay un commit pendiente”: puede ser un add sin commit.

Empty index, repo nuevo, verificado: git init -q -b main && git write-tree4b825dc642cb6eb9a060e54bf8d69288fbee4904 (el empty tree canónico). Exit 0. No es un error.

Trampas que ya vimos

  1. “write-tree es el commit”. No. commit-tree es el commit. write-tree es el directorio. HEAD no se mueve. git status sigue mostrando staged files.
  2. “El working tree sucio cambia el SHA”. No. Solo el index. Sin add, el SHA es el de la última vez que actualizaste el index.
  3. Conflictos. 128 + error building trees. No hay --unmerged. Resuelve o git merge --abort. No inventes un tree a medias.
  4. --missing-ok “por si el pack está raro”. El check existe para no serializar un índice que apunta a objetos que no tienes. Apagarlo fabrica un tree que fsck va a odiar.
  5. --prefix con path que no está en el index. 128, prefix … not found. El slash final es del SYNOPSIS; no lo improvises.
  6. REST Create a tree + Create a commit + Update a ref. Tres writes. Un agente autónomo no publica historia por API. El equivalente local ya está prohibido (commit-tree + update-ref).
  7. Comparar el SHA del tree con el SHA de un blob. hash-object de a.txt ≠ write-tree. Uno es file, el otro es directorio.
  8. Dump del tree al modelo. ls-tree -r de un repo grande es un volcado. Pathspec o -d.

Checklist

  • Worktree propio, no el checkout del humano.
  • git status -sb sin UU / AA / DD.
  • Quiero el SHA del index, no un commit.
  • Un solo git write-tree. Sin flags.
  • Si necesito paths: ls-tree del SHA, no el tree crudo al LLM.
  • Cero commit-tree, cero update-ref, cero POST de trees.
  • Cero --missing-ok. Cero --prefix sin un humano que nombre el subdir.
  • Si el ticket pide “commitear”, commit -m, no este verbo.

FAQ

¿write-tree cambia archivos? No. Solo lee el index y (si hace falta) escribe un objeto tree. El working tree queda igual.

¿Puedo usarlo como dry-run de commit? A medias. Te dice el tree que tendría el próximo commit si commiteas el index ahora. No valida el mensaje, no corre hooks, no mueve la rama. Para el commit real, commit.

¿Por qué el empty tree tiene un SHA famoso? Porque el contenido del tree vacío es siempre el mismo. Git lo reusa. No lo hardcodes en scripts de agente: pídelo con git write-tree en un index vacío, o con git hash-object -t tree --stdin </dev/null si un humano lo pide. El SHA verificado hoy es 4b825dc642cb6eb9a060e54bf8d69288fbee4904.

¿Y update-index? El man lo nombra: sin actualizar el index, write-tree no ve el disco. El agente autónomo actualiza el index con add de paths explícitos, no con plumbing --cacheinfo.

¿Dónde encaja esto? En el hub de comparativas y decisiones, junto a hash-object y ls-tree. Si estás armando el primer agente, empieza por el curso.

un index con conflicto no produce tree; 128 y error building trees