git commit-tree para coding agents: commit sin mover HEAD
Resumen
git commit-tree fabrica un objeto commit a partir de un tree y lo imprime en stdout. No actualiza HEAD, no corre hooks, no toca el working tree. Un agente no lo encadena a update-ref ni a REST Create a commit. Distinto de commit, write-tree y hash-object. 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 commit-tree crea un objeto commit. El man (git-commit-tree(1); git-scm.com/docs/git-commit-tree 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): Create a new commit object. SYNOPSIS: git commit-tree <tree> [(-p <parent>)...] o con -m / -F / -S. Imprime el SHA del commit a stdout. HEAD, índice, working tree y refs no se mueven.
El man abre con: This is usually not what an end user wants to run directly. See git-commit(1) instead. Eso no es adorno. commit escribe el objeto y actualiza la rama. commit-tree solo escribe el objeto. El siguiente paso típico en scripts humanos es update-ref. Un agente no encadena esa tubería.
GitHub Docs (REST API endpoints for Git commits, HTTP 200 2026-09-06, API version 2026-03-10): Create a commit pide Contents write, message + tree obligatorios, responde 201. parents vacío = root commit. Ninguno de esos POSTs mueve una rama. Un agente de lectura no llama el endpoint.
Contrato: inspeccionar un SHA ya existente, cero autónomo, cero -p inventado, cero -S, cero update-ref, cero REST de escritura.
Qué hace (y qué no)
Toma un <tree> existente y emite un commit. El man: Creates a new commit object based on the provided tree object and emits the new commit object id on stdout. El mensaje sale de stdin salvo que pases -m o -F. Cero padres = root. Un padre = commit normal. Más de un padre = merge.
Verificado 2026-09-06 (Git 2.50.1 / Apple Git-155). Repo nuevo, echo hello > a.txt && git add a.txt && git write-tree → tree 2e81171448eb9f2ee3821e3d447aa6b2fe3ddba1. Luego:
git commit-tree 2e81171448eb9f2ee3821e3d447aa6b2fe3ddba1 -m first
Imprime un SHA de 40 hex. cat-file -t → commit. -p muestra tree 2e8117…, author / committer y el párrafo first. status -sb sigue en ## No commits yet on main con A a.txt. rev-parse HEAD → ambiguous argument 'HEAD'. El objeto existe. La rama no.
No es write-tree. write-tree serializa el index a un tree. commit-tree envuelve un tree ya existente en un commit. El SHA del tree no es el SHA del commit.
No es commit. commit corre hooks, pide mensaje, mueve HEAD (o deja detached). commit-tree no corre pre-commit: verificado, hook exit 1 + echo HOOK_RAN → el comando igual imprime SHA y exit 0; el archivo del hook queda vacío. Tampoco hay --dry-run. -h → usage, 129.
No es hash-object. hash-object hashea un blob. Un blob como <tree> falla: is not a valid 'tree' object, 128. Un commit como <tree> falla igual. Un tree como -p falla: is not a valid 'commit' object, 128.

Lo que el agente sí / no corre
| Quiero | Comando | Trampa |
|---|---|---|
| Ver un commit ya hecho | cat-file -p <sha> | fabricar otro con commit-tree |
| SHA del index | write-tree | asumir que ese SHA es un commit |
| Grabar el trabajo | add + commit -m | commit-tree + update-ref |
| ¿HEAD existe? | rev-parse --verify --quiet HEAD | commit-tree “para inicializar” |
| Firmar | verify-commit sobre un SHA existente | -S autónomo |
Prohibido en autónomo:
- Correr
git commit-treepara “hacer el commit”. El man lo dice: no es lo que un usuario final quiere. Si el ticket pide un commit, el camino es porcelain en un worktree propio. - Encadenar a update-ref. Eso mueve la ref. commit-tree solo deja basura reachable-or-not en el object store. Un SHA huérfano no es un PR.
-pcon un SHA que no es commit. Objeto inexistente: is not a valid object, 128. Tree como padre: is not a valid 'commit' object, 128.-S/--gpg-signautónomo. Verificado: sin clave → gpg failed to sign the data / No secret key, 1. No hay commit a medio firmar útil.- Create a commit (
POST /repos/{owner}/{repo}/git/commits). GitHub Docs: Contents write,message+treerequired,parentsomitido = root, 201. No mueve la rama. Un agente de lectura no lo llama. - Inventar
GIT_AUTHOR_DATE/GIT_COMMITTER_DATEpara “backdatear”. El man documenta formato interno<unix> <offset>, RFC 2822 e ISO 8601. Verificado:1112904793 +0200sí pega+0200en el objeto. ISO sin offset se interpreta en local (-0600aquí). Un agente no reescribe autoría. - Meter el SHA nuevo en un prompt largo. Un commit SHA no es un diff. Si necesitas el mensaje,
git cat-file -po show-s.
-m y -F se pueden repetir: cada uno es un párrafo, en el orden dado. -F - lee stdin. -F a un path que no existe: could not open … for reading, 128. Sin -m/-F, el man espera stdin hasta ^D — un agente no se queda colgado: siempre pasa -m. El empty tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 sí acepta commit-tree; no lo uses para “inicializar main”.
Receta (60 segundos)
Solo si el humano pidió explícitamente commit-tree, y solo en un worktree propio:
git status -sb
git rev-parse --verify --quiet HEAD^{tree}
git cat-file -t <tree>
git commit-tree <tree> -p HEAD -m "mensaje corto"
Cuatro líneas. status te dice si hay UU o untracked que el tree no incluye. HEAD^{tree} es el tree del tip, no el del index. cat-file -t tiene que decir tree antes de commit-tree. El -p HEAD solo si HEAD existe: en un repo vacío, -p HEAD ni siquiera llega — no hay padre, y eso es un root.
Después para. No corras git update-ref. No corras git reset --hard al SHA nuevo. Enséñale el SHA al humano.
Para el caso normal (el 99 %):
git add path/a path/b
git commit -m "feat: mensaje"
Eso sí mueve la rama, sí corre hooks, sí deja un commit que un PR puede ver.
Trampas que rompen el repo (o la sesión)
Objeto del tipo equivocado. Blob o commit como <tree> → 128. Tree como -p → 128. El mensaje is not a valid 'tree' object / 'commit' object es el contrato. No reintentes con --literally ni con hash-object -t commit.
Repo sin HEAD. commit-tree funciona igual (root commit). git status sigue diciendo No commits yet. El humano cree que “ya hay historia”. Mentira: hay un objeto suelto. Sin update-ref, git gc puede recogerlo.
Merge a ciegas. Dos -p fabrican un merge. Verificado: commit-tree <tree> -p C1 -p C2 -m merge escribe dos líneas parent. No hay conflicto, no hay index, no hay working tree. Es un merge mentiroso si el tree no es el resultado de fusionar. Un agente no inventa merges.
Hooks saltados. pre-commit, commit-msg, prepare-commit-msg no corren. Un linter que vive en el hook no te salva. Si el repo exige Conventional Commits vía hook, commit-tree lo ignora.
REST paralelo. Create a commit en GitHub no actualiza refs/heads/…. El siguiente Update a reference sí. Un agente no lo dispara. -F a un path del working tree mete código en el log: usa -m corto.

FAQ
¿Puedo usarlo para no disparar hooks? No. Si el humano quiere saltarse un hook, eso es una decisión humana (--no-verify en commit, y esa guía ya lo veta en autónomo). commit-tree no es un bypass “técnicamente más limpio”: es otra superficie.
¿Y para un commit raíz en un repo nuevo? Tampoco. git commit con el index staged crea el root y mueve main. commit-tree deja el objeto y status sigue en No commits yet.
¿commit-tree respeta user.name? Sí: author y committer salen de la identidad configurada. No reescribas GIT_AUTHOR_* para fingir a otra persona.
Checklist
- El ticket pide commit-tree por nombre, no “haz un commit”.
- Worktree propio. Cero índice del humano.
-
git cat-file -t <tree>=treeantes de llamar. -
-mcorto. Cero stdin colgado. Cero-Fa código. - Cero
-S. Ceroupdate-ref. Cero REST write. - Reporta el SHA. No lo dejes de HEAD.
Fuentes
Man git-commit-tree(1) y git-commit(1) (Git 2.50.1 / Apple Git-155, 2026-09-06). git-scm.com/docs/git-commit-tree y git-scm.com/docs/git-commit, HTTP 200, last-modified 2026-08-31. GitHub Docs REST API endpoints for Git commits (API 2026-03-10): Create a commit Contents write, 201. SEE ALSO del man: git-write-tree(1), git-commit(1).
Siguiente lectura: write-tree (el tree que este comando envuelve), commit (el porcelain que sí quieres), update-ref (el paso que no encadenas). Hub: comparativas y decisiones. Curso: /curso/instalar-agente.
Lecturas relacionadas
Sigue explorando Coding Agents y otras piezas para builders.

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

git mktree para coding agents: tree desde ls-tree, no desde el index

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