Guía9 min

git describe para coding agents: nombre legible, no un Release

Resumen

git describe nombra un commit con el tag anotado más cercano. Default ignora lightweight; --tags los incluye. Sin match exacto imprime tag-N-gSHA. --dirty no admite commit-ish. --exact-match falla si no hay tag en el tip. Un agente no lo usa como GitHub Release ni como git tag. Git 2.50.1.

GitHub
Un commit se nombra con el tag anotado más cercano; el working tree no se mueve

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 describe no crea un tag ni un GitHub Release. El man (git-describe(1), Git 2.50.1 / Apple Git-155; git-scm.com/docs/git-describe HTTP 200, last-modified 2026-08-31; last updated in 2.55.0, 2026-06-29) busca el tag alcanzable más reciente desde un commit-ish (HEAD si omites el argumento). Si el tag apunta exactamente a ese commit, imprime solo el nombre. Si no, añade cuántos commits hay encima y una abreviatura del objeto: v1.2.3-14-g2414721. Ese string también sirve como nombre de objeto para otros comandos Git.

Default sin --all ni --tags: solo tags anotados. Lightweight no cuentan. GitHub Docs (About releases, HTTP 200 2026-09-04): un Release se basa en un Git tag, pide write para gestionarlo y empaqueta zip/tarball. describe no publica nada.

Esta guía no sustituye tag (crear o listar refs/tags/) ni show (inspeccionar el objeto). El contrato: qué forma corre un agente, qué exit code significa, y por qué el default no ve un tag ligero.

Tres salidas, un commit

PreguntaComandoQué imprime
¿Qué nombre tiene HEAD?git describetag anotado, o tag-N-gSHA
¿Hay un tag exacto en este commit?git describe --exact-matchel tag, o fatal 128
¿El working tree está sucio?git describe --dirtyigual que HEAD, o sufijo -dirty
Incluir lightweightgit describe --tagsel tag más cercano en refs/tags/
Fallback si no hay tagsgit describe --alwaysSHA abreviado único

El man (DESCRIPTION): el número extra es el de commits que mostraría git log <tag>..<commit>. El sufijo es -g + abreviatura del tip (la g es de Git, no un SHA). La longitud escala con el tamaño del repo; default mínimo 7. --abbrev=0 suprime el formato largo y deja solo el tag más cercano.

Exit verificado 2026-09-04 (Git 2.50.1 / Apple Git-155): sin tags → No names found, cannot describe anything., 128. Solo lightweight → No annotated tags can describe '…'. However, there were unannotated tags: try --tags., 128. --tags sobre ese lightweight → el nombre, 0. Objeto inválido → Not a valid object name, 128. Tree → is neither a commit nor blob, 128.

Default usa tags anotados; lightweight exige --tags

Lo que el agente sí / no corre

QuieroComandoTrampa
Nombre de HEADgit describegit tag “para ver la versión”
¿Este commit es un tag?git describe --exact-match -- HEADparsear a ojo el string largo
Versión + suciogit describe --dirty--dirty HEAD
Lightweightgit describe --tags --abbrev=0asumir que default los ve
Sin tags en el repogit describe --alwaysinventar v0.0.0

Prohibido en autónomo:

  • Tratar la salida como un GitHub Release. GitHub Docs: el Release es un paquete (notas, binarios, zip/tarball del tag). Hasta 1000 assets; cada file < 2 GiB. describe no sube assets ni pide write.
  • Crear o mover tags “para que describe funcione”. Eso es tag. Aquí solo lees.
  • --dirty con un commit-ish. Verificado: git describe --dirty HEADoption '--dirty' and commit-ishes cannot be used together, 128. --dirty / --broken describen el working tree de este checkout.
  • --all como “versión”. El man: cualquier ref en refs/ (branch, remote-tracking, lightweight). Verificado: con rama más cercana que el tag, imprime heads/main, no v1. Eso no es SemVer.
  • --debug volcado al LLM. El man: la estrategia va a stderr; el nombre sigue en stdout. No es API.
  • --contains para “el tag de este commit”. El man: busca el tag que viene después (implica --tags). Verificado: HEAD sin tag posterior → cannot describe, 128.
  • --exact-match / --candidates=0 y tragar el fatal. Verificado: commit con un commit encima del tag → no tag exactly matches, 128.
  • Describir un tree. El man (BUGS): trees y tags que no apuntan a commits no se describen. Un blob sí: <commit-ish>:<path> del primer commit en un walk inverso desde HEAD. Verificado: blob de fv1-1-g…:f. Lightweight que apunta al blob se ignora.
  • Encadenar la salida a commit o a un tag -f. El string nombra un objeto; no es un permiso.

--broken[=<mark>]: si el repo está corrupto y Git no puede saber si hay cambios locales, el default erra; --broken añade -broken (o el mark). Verificado en un árbol sucio y sano: git describe --broken sigue con -dirty, no inventa -broken.

Receta (60 segundos)

Solo en un worktree propio:

git status -sb
git describe
git describe --dirty
git describe --exact-match; echo $?

Si el humano pide “versión aunque no haya tags anotados”:

git describe --tags --always --dirty

--always (man): SHA abreviado único como fallback. Verificado: repo sin tags → 12f5b02, exit 0. Un agente que loguea “versión” en un clone fresco usa esto; no finge un tag.

--long: siempre tag-N-gSHA, incluso si el commit es el tag (v1.2-0-gdeadbee). Verificado: checkout del tag anotado v1git describe --long = v1-0-g12f5b02; --abbrev=0 = v1.

--first-parent: el walk no entra al segundo padre de un merge. Sirve para no casar tags de una rama mergeada. Verificado: merge --no-ff de topic (tag v1.1) sobre main (tag v1) → default v1.1-3-g…; --first-parentv1-2-g…. Un agente de “versión de main” pasa --first-parent si el ticket lo pide; no lo adivina.

--dirty marca el working tree; no se combina con un commit-ish

UI de GitHub vs clone local

GitHub Docs (About releases): cualquiera con read ve y compara Releases; write para gestionarlos. La fecha del tag puede diferir de la del Release. git describe corre en tu clone: ve tags que hayas fetcheado, no el paquete de github.com.

--match <glob> (sin el prefijo refs/tags/): solo tags que casan. Con --all, también branches y remotes, sin refs/heads/ ni refs/remotes/. Varios --match se acumulan (OR). --exclude resta. Verificado: --match 'v1*' nombra; --match 'nope*'No names found, 128.

--candidates=<n>: default 10 tags recientes. 0 = solo exacto (--exact-match). Subirlo retrasa un poco y puede acercar el tag. Un agente one-shot no sube el número “por si acaso”.

SEARCH STRATEGY del man: primero match exacto (anotados ganan a lightweight; fecha más nueva gana). Si no, camina ancestros y elige el tag con menos commits de diferencia (git log tag..input mínimo).

Checklist

  • Worktree propio. git status -sb. Default = HEAD.
  • Default = anotados. Lightweight → --tags. Cero tags → --always, no un SemVer inventado.
  • --exact-match si el ticket es “¿este commit es un tag?”. Fatal 128 = no.
  • --dirty sin commit-ish. --broken solo si el ticket admite repo corrupto.
  • Cero --all como versión, cero --debug al LLM, cero Release.
  • Crear el tag es tag. Inspeccionar el objeto es show.

FAQ

¿describe es lo mismo que git tag --list? No. tag lista o crea refs. describe nombra un commit con el tag más cercano. Un listado no te dice cuántos commits hay encima.

¿Por qué no veo v0 lightweight? El man: default solo anotados (-a / -s en tag). --tags abre refs/tags/ entero.

¿Puedo parsear tag-N-gSHA por guiones? El tag mismo puede traer guiones. El man fija el sufijo como -<n>-g<abbrev>. Si necesitas el SHA, log -1 --format=%H, no recortes a ciegas.

El curso instalar un agente cubre el loop local. Hub: comparativas y decisiones. describe no es tag: nombra un commit; no mueve refs/tags/.