Guía9 min

git tag para coding agents: listar refs, no publicar releases

Resumen

git tag crea, lista, borra o verifica refs en refs/tags/. Un agente lista con --list y patrón; no -f, no -d, no -s/-u, no editor. Annotated pide -m; lightweight es un puntero. GitHub Releases se basan en tags y piden write. Git 2.50.1.

GitHub
Un tag apunta a un commit; el working tree y HEAD 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 tag no publica un release. El man (git-tag(1), Git 2.50.1 / Apple Git-155; git-scm.com/docs/git-tag HTTP 200, last updated in 2.55.0): crea, lista, borra o verifica un tag. Salvo -d / -l / -v, añade una ref en refs/tags/. Sin -f, el nombre no puede existir. HEAD, index y working tree no se mueven.

Esta guía no sustituye push (ahí --tags está prohibido) ni commit. El contrato: qué forma corre un agente, qué flags fuerzan, y por qué un tag no es un GitHub Release.

Lista, no pager, no crear por accidente

Sin argumentos, o con -l / --list, lista. El man: el patrón es un glob de shell (fnmatch(3)). Varios patrones: basta que uno coincida. --list queda implícito si pasas un filtro de listado (--contains, --merged, --points-at). pager.tag solo aplica al listado; un agente no pagina.

--format interpola %(fieldname) como git-for-each-ref. Default: %(refname:strip=2). Para un agente, --format='%(refname:strip=2)' o --list 'v*' bastan. -n imprime líneas de anotación y implica --list. No dumps.

--contains / --no-contains, --merged / --no-merged, --points-at: el commit omitido es HEAD. NOTES del man: varios --contains y --no-contains a la vez = tags que contienen al menos uno de los contains y ninguno de los no-contains. Lo mismo para merged. No son el mismo filtro.

Listado con --list; el working tree no se mueve

Lo que el agente sí / no corre

QuieroComandoTrampa
Listargit tag --list 'v*'git tag v* sin --list (el man: sin args lista; un token suelto es crear)
Ver a qué apuntagit rev-parse <tag>git show largo
Annotated local (humano pidió)git tag -a <name> -m '<msg>' <commit>-a sin -m (abre editor; en esta máquina, exit 1 si EDITOR vacío)
Lightweightgit tag <name> <commit>Tag de “release” sin objeto
Verificar firmagit tag -v <name>Crear -s “por si acaso”

Prohibido en autónomo:

  • -f / --force. El man: sin -f, se niega si el tag ya existe. Con -f, reemplaza el nombre. Verificado 2026-09-04 en esta máquina (Git 2.50.1 / Apple Git-155): segundo git tag v-testfatal: tag 'v-test' already exists, exit 128; git tag -f v-test → exit 0. DISCUSSION On Re-tagging: si nunca empujaste, -f local cierra el error. Si otros ya vieron el tag, el man llama a reusar el mismo nombre “the insane thing”: dos clones pueden tener “X” distinto. Git no pisa el tag del otro en un pull. El arreglo sano es otro nombre.
  • -d / --delete. Borra tags locales. No deshace un tag ya empujado. Un agente no limpia refs/tags ajenos.
  • -s / -u / --local-user. Crea un tag firmado. El backend lo marca gpg.format (default OpenPGP). Sin passphrase/agente, el comando se queda o falla. tag.gpgSign firma todos los tags; el man avisa que en un script eso dispara muchas firmas. Un agente no firma.
  • -e / --edit. Abre editor sobre -m / -F. Un agente no edita el mensaje a ciegas.
  • -F - (stdin) o -F a un path que el humano no pidió.
  • GIT_COMMITTER_DATE para antedatar (On Backdating Tags). Eso es import de otro VCS, no un release de agente.
  • Empujar tags. Push: --tags manda todo refs/tags; --follow-tags manda anotados alcanzables. Cero. Un tag local no es un Release.

-- delante del nombre. Un tagname que parece opción se parsea mal.

Receta (60 segundos)

Solo en un worktree propio, y solo si el humano pidió un tag:

git tag --list 'v*'
git check-ref-format "refs/tags/${name}"
git rev-parse --verify "${commit}^{commit}"
git tag -a "$name" -m "$msg" "$commit"   # o lightweight si el contrato lo pide
git tag --list "$name"

Si el humano no pidió tag: no crees ninguno. Cierre de trabajo = PR, no v1 en HEAD.

Listar sin crear:

git tag --list
git tag --list 'v*'
git tag --points-at HEAD
git tag --contains HEAD

--points-at: tags de ese objeto. --contains: tags cuyo historial incluye el commit (quién se “rompe” si lo reescribes).

 -f reemplaza un tag existente; un pull ajeno no pisa el tag viejo

Annotated vs lightweight vs GitHub Release

El man: -a / -s / -u crean un tag object (fecha, tagger, mensaje, firma opcional) y piden mensaje. Sin -m / -F / --trailer, arranca editor. Verificado aquí: git tag -a v-ann sin -mPlease supply the message using either -m or -F option., exit 1. Con -m y sin -a/-s/-u, se implica -a.

Sin esas flags: lightweight — un nombre que apunta al objeto (casi siempre un commit). El man: annotated es para release; lightweight, etiqueta privada o temporal. Por eso git describe ignora lightweight por default.

Nombres. El man: el tagname nuevo tiene que pasar git-check-ref-format(1) (git-scm.com/docs/git-check-ref-format HTTP 200). Misma familia de reglas que otras refs: nada de espacio, .., .lock, @{, ~, ^, :, ?, *, [. Un agente valida antes de crear.

GitHub Docs (About releases, HTTP 200 2026-09-04): un Release empaqueta software, notas y binarios para que otros lo bajen. Se basa en un Git tag — marca un punto del historial. La fecha del tag y la del Release pueden no coincidir: se crean en momentos distintos. Quien tiene read ve y compara; write hace falta para gestionar Releases (Managing releases in a repository, HTTP 200: colaboradores y write pueden crear, editar y borrar un Release). Hasta 1000 assets por Release; cada archivo < 2 GiB; no hay tope documentado de tamaño total ni de ancho de banda. GitHub añade zip/tarball del árbol en el tag.

Un git tag local no crea ese Release. No adjunta assets. No genera notas. No notifica suscriptores. Publicar el Release es decisión humana (UI, gh release, API). Un agente no lo dispara.

tag.forceSignAnnotated / tag.sort / tag.gpgSign viven en config. Un agente no los enciende para “cumplir”.

Checklist

  • Worktree propio. El humano pidió un tag; si no, no crees.
  • Listar: --list + patrón. Cero token suelto que se lea como creación.
  • Nombre: git check-ref-format refs/tags/<name> exit 0. -- si parece opción.
  • Annotated: -a -m. Cero editor, -e, -F -.
  • Cero -f, -d, -s, -u, GIT_COMMITTER_DATE.
  • Cero push --tags / --follow-tags.
  • Cierre = PR, no Release.

FAQ

¿git tag v1 en cada merge? No. Lightweight no es release. El man reserva annotated para publicar.

¿Puedo -f porque “el SHA estaba mal”? Si no salió del repo, sí en local. Si alguien pudo leer el tag, otro nombre. El man: Git no reescribe el tag del otro en un pull.

¿git push origin v1? Empuja esa ref. Sigue siendo publicar un tag. En autónomo, no. El Release de GitHub es otro paso, con write.

¿--list vs git tag a secas? A secas lista. Un argumento extra crea. Siempre --list si hay patrón.

El curso instalar un agente cubre el loop local. Hub: comparativas y decisiones. Tag no es push: mueve refs/tags/, no el remoto.

Verificado 2026-09-04 contra git-tag(1) (Git 2.50.1 / Apple Git-155; git-scm.com/docs/git-tag HTTP 200, last updated in 2.55.0), git-check-ref-format(1) (HTTP 200) y GitHub Docs “About releases” y “Managing releases in a repository” (HTTP 200; Releases se basan en tags; write para gestionar; 1000 assets, archivo < 2 GiB). En esta máquina: tag duplicado exit 128; -a sin -m exit 1; -a -m exit 0; -f exit 0.