git mktree para coding agents: tree desde ls-tree, no desde el index
Resumen
git mktree lee stdin en formato ls-tree no recursivo y escribe un tree. Normaliza el orden. Verifica que cada hash exista, salvo --missing. Gitlinks pueden faltar. Cero -r, cero --missing autónomo, cero REST Create a tree. Distinto de write-tree, hash-object y read-tree. 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 mktree no lee el index. El man (git-mktree(1); git-scm.com/docs/git-mktree HTTP 200, last-modified 2026-08-31; last updated in 2.43.0, 2.43.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): Build a tree-object from ls-tree formatted text. SYNOPSIS: git mktree [-z] [--missing] [--batch]. Lee stdin en formato no recursivo de ls-tree. Imprime el SHA del tree a stdout. HEAD, index y working tree no se mueven.
El orden de las entradas se normaliza. El man: The order of the tree entries is normalized by mktree so pre-sorting the input is not required. Por defecto verifica que cada hash exista. --missing apaga ese check. Los gitlinks (160000) siempre pueden faltar: This option has no effect on the treatment of gitlink entries (aka "submodules") which are always allowed to be missing.
GitHub Docs (REST API endpoints for Git trees, HTTP 200 2026-09-06): Create a tree pide Contents write y responde 201. Un agente de lectura no llama el POST. mktree es el equivalente local: fabrica un tree sin commit y sin rama.
Contrato: stdin de un solo nivel, cero --missing, cero --batch autónomo, cero REST de escritura.
Qué hace (y qué no)
Crea un objeto tree a partir de líneas ls-tree. El mismo input canónico, el mismo SHA. Verificado 2026-09-06 (Git 2.50.1 / Apple Git-155): echo hello > a.txt && git add a.txt && git commit -qm init. git ls-tree HEAD → 100644 blob ce013625… a.txt. Ese pipe a git mktree imprime 2e81171448eb9f2ee3821e3d447aa6b2fe3ddba1. write-tree imprime el mismo SHA. cat-file -t → tree, -s → 33. git status -sb sigue ## main. HEAD no se movió.
No es write-tree. write-tree serializa el index. mktree serializa texto. Si el index y el texto no coinciden, los SHA tampoco.
No es hash-object. hash-object hashea un blob (o un tipo con -t). mktree ensambla nombres + modos + hashes en un directorio. El SHA de a.txt no es el SHA del tree.
No es read-tree. read-tree escribe el index. mktree no toca el index.
No es commit. No hay mensaje, no hay padre, no hay rama. El plumbing siguiente sería git commit-tree <tree> — un agente no lo encadena.
Stdin vacío: printf '' | git mktree → 4b825dc642cb6eb9a060e54bf8d69288fbee4904 (empty tree), exit 0. No es un error.

Lo que el agente sí / no corre
| Quiero | Comando | Trampa |
|---|---|---|
| Tree idéntico a HEAD | git ls-tree HEAD | git mktree | asumir que es un commit |
| Tree del index | write-tree | alimentar mktree con ls-files |
| Ver el contenido | ls-tree <sha> | pegar el tree al LLM |
| ¿Falta un blob? | mktree sin --missing | --missing “por si acaso” |
| Submódulo | línea 160000 commit <sha>\tpath | exigir que el commit exista |
Prohibido en autónomo:
--missing. El man: The default behaviour (without this option) is to verify that each tree entry's hash identifies an existing object. Verificado:100644 blob deadbeef… missing.txtsin flag → fatal: entry 'missing.txt' object deadbeef… is unavailable, 128. Con--missing→ SHA74c59fb4…, 0. Un tree que apunta a blobs que no están es un objeto podrido.- Input recursivo.
git ls-tree -r HEAD | git mktreeconsub/b.txt→ fatal: path sub/b.txt contains slash, 128. El man dice non-recursive ls-tree output format. Un nivel. Los subdirectorios van como entrada040000 tree <sha>\tdir. --batchautónomo. El man: Allow building of more than one tree object before exiting. Each tree is separated by a single blank line. Verificado: dos copias dels-tree HEADseparadas por blanco → dos SHA iguales, 0. Sirve para scripts de import; no para “arma el repo”.- Create a tree (
POST /repos/{owner}/{repo}/git/trees). GitHub Docs: Contents write, arraytreeobligatorio, 201. Si omitesbase_tree, los paths no listados salen borrados en el próximo commit. Un agente de lectura no lo llama. - Encadenar a
commit-tree+update-ref. Eso fabrica historia. Si el ticket pide un commit, add + commit-men un worktree propio. - Tratar argumentos extra como tree-ish. Verificado:
git mktree HEADcon stdin vacío imprime el empty tree y sale 0. IgnoraHEAD. No “hace el tree de HEAD”. El input es stdin, punto.
-h: usage, 129. --foo: *unknown option foo'*, **129**. Fuera de un repo: *fatal: not a git repository*, **128**. Línea malformada: *fatal: input format error: …*, **128**. Espacio en vez de tab: el mismo 128. No hay -n` dry-run. No hay pathspec.
Receta (60 segundos)
Solo en un worktree propio, y solo si un humano pide el SHA de un tree armado a mano:
git status -sb
git ls-tree HEAD
git ls-tree HEAD | git mktree
Tres líneas. status confirma que no estás fabricando sobre un merge sucio por accidente. ls-tree te muestra el formato. mktree imprime 40 hex. Si el SHA coincide con write-tree, el texto y el index hablan del mismo directorio.
Para un tree nuevo (blob ya en el object store):
printf '100644 blob %s\t%s\n' "$(git hash-object -w a.txt)" a.txt | git mktree
hash-object -w escribe el blob. Sin -w, mktree sin --missing falla 128 si el objeto no existe. Un agente no inventa modos (100755, 120000) ni nombres con slash.
-z solo si la fuente es git ls-tree -z. El man: Read the NUL-terminated ls-tree -z output instead. Mezclar -z con newlines parte mal las líneas.
Trampas que ya vimos
- “mktree es write-tree”. No. write-tree lee el index. mktree lee texto. Mismo SHA solo si el texto es el ls-tree de ese index.
- “Le paso
-rpara no perder subdirs”. 128, contains slash. Los trees anidados se arman de abajo hacia arriba: mktree del hijo, luego una línea040000 tree <sha>\tnombreen el padre.--batchexiste para eso; un agente no lo improvisa. --missing“el SHA lo pongo después”. El check existe para no serializar punteros huecos. Apagarlo fabrica un tree quefsckva a odiar. Los gitlinks ya están exceptuados: verificado,160000 commit deadbeef… submodsin--missingsale 0.git mktree HEADcomo atajo. Stdin vacío → empty tree. Crees que leíste HEAD; escribiste el árbol vacío. El atajo correcto esgit ls-tree HEAD | git mktreeo, si quieres el index, write-tree.- REST Create a tree + Create a commit + Update a ref. Tres writes. El equivalente local ya está prohibido. Además, sin
base_treeGitHub borra lo no listado. - Duplicar el path. Verificado: dos líneas
a.txtiguales → SHA distinto, exit 0. Git no protesta. El tree queda raro. No dedupes a ciegas: aborta. - Dump al modelo. Un ls-tree de un monorepo es un volcado. Pathspec en ls-tree, no mktree de mil líneas.
- Orden “hay que sort”. No. mktree normaliza. No pongas
sorten el pipe “por si acaso”: el formato lleva tabs y modos; un sort mal puesto parte líneas.
Checklist
- Worktree propio, no el checkout del humano.
- Quiero un tree SHA a partir de texto ls-tree, no un commit.
- Input de un nivel. Cero paths con
/. - Cada blob/tree hash existe (
git cat-file -e). Cero--missing. - Un solo
git mktree. Sin flags, salvo-zsi la fuente es-z. - Si necesito el index: write-tree, no este verbo.
- Cero
commit-tree, ceroupdate-ref, cero POST de trees. - Cero
--batchsin un script de import que el humano nombró. - Si el ticket pide “commitear”, commit
-m.
FAQ
¿mktree cambia archivos? No. Solo lee stdin y (si hace falta) escribe un objeto tree. El working tree y el index quedan iguales.
¿Puedo usarlo como dry-run de commit? No. Te da un tree. No valida mensaje, no corre hooks, no mueve la rama. El tree del próximo commit, si el index está limpio, sale más barato con write-tree.
¿Por qué el empty tree sale con stdin vacío? Porque cero entradas es un tree válido. Git reusa 4b825dc642cb6eb9a060e54bf8d69288fbee4904. No lo hardcodes: pídelo con stdin vacío o con write-tree en un index vacío.
¿Y hash-object -t tree? Eso hashea bytes crudos de un tree. mktree parsea líneas ls-tree, ordena y arma el objeto. No son el mismo contrato. Un agente no pega un dump binario a hash-object.
¿Dónde encaja esto? En el hub de comparativas y decisiones, junto a ls-tree y write-tree. Si estás armando el primer agente, empieza por el curso.

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

git checkout-index para coding agents: index al disco, no checkout

git commit-tree para coding agents: commit sin mover HEAD

git read-tree para coding agents: carga el índice, no el disco
