Guía9 min

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.

GitHub
Un tree se convierte en objeto commit; HEAD y el working tree permanecen intactos

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 -tcommit. -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 HEADambiguous 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.

commit-tree escribe el objeto; HEAD y el disco no se enteran

Lo que el agente sí / no corre

QuieroComandoTrampa
Ver un commit ya hechocat-file -p <sha>fabricar otro con commit-tree
SHA del indexwrite-treeasumir que ese SHA es un commit
Grabar el trabajoadd + commit -mcommit-tree + update-ref
¿HEAD existe?rev-parse --verify --quiet HEADcommit-tree “para inicializar”
Firmarverify-commit sobre un SHA existente-S autónomo

Prohibido en autónomo:

  • Correr git commit-tree para “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.
  • -p con 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-sign autó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 + tree required, parents omitido = root, 201. No mueve la rama. Un agente de lectura no lo llama.
  • Inventar GIT_AUTHOR_DATE / GIT_COMMITTER_DATE para “backdatear”. El man documenta formato interno <unix> <offset>, RFC 2822 e ISO 8601. Verificado: 1112904793 +0200 sí pega +0200 en el objeto. ISO sin offset se interpreta en local (-0600 aquí). 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 -p o 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 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.

límites: tipo de objeto, HEAD intacto, hooks saltados, cero REST write

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> = tree antes de llamar.
  • -m corto. Cero stdin colgado. Cero -F a código.
  • Cero -S. Cero update-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.