git update-ref para coding agents: plumbing de refs, no mover HEAD
Resumen
git update-ref escribe el SHA de una ref con compare-and-swap. Dos args mueven HEAD sin checkout y dejan el working tree atrás. Tres args exigen old-oid. Cero autónomo. Cero -d, --stdin o --no-deref. Distinto de branch, symbolic-ref y reset. 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 update-ref actualiza de forma segura el object name guardado en una ref. El man (git-update-ref(1); git-scm.com/docs/git-update-ref 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): Update the object name stored in a ref safely. SYNOPSIS: git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>] · git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>] · git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates].
Es plumbing. No lista. No hace checkout. No toca el índice ni el working tree. Solo escribe (o borra) el puntero. Por eso es más peligroso que branch: git branch feat crea una ref y te deja donde estás. git update-ref HEAD <oid> mueve la rama actual y el árbol se queda en el commit anterior. El man lo dice: Given two arguments, stores the <new-oid> in the <ref>, possibly dereferencing the symbolic refs. E.g. git update-ref HEAD <new-oid> updates the current branch head to the new object.
No es symbolic-ref. Ese comando lee o reescribe punteros simbólicos (ref: refs/heads/…). update-ref sin opciones does not support updating a symbolic ref to point to another ref. Para eso el man remite a git-symbolic-ref(1). --stdin sí trae symref-*; un agente no entra ahí.
Tampoco es reset. reset mueve HEAD y, según el modo, el index y el disco. update-ref solo mueve el puntero. El working tree queda detrás del tip. Verificado abajo: dos args sobre HEAD dejan M en el archivo y status en la misma rama.
Ni show-ref ni rev-parse: esos leen. update-ref escribe.
GitHub Docs (REST API endpoints for Git references, HTTP 200 2026-09-06): A Git reference (git ref) is a file that contains a Git commit SHA-1 hash. Get a reference pide Contents read. Create / Update / Delete a reference piden Contents write. Eso es github.com, no el clone local. Un POST a /git/refs no sustituye update-ref en el worktree del humano.
Contrato: no corras git update-ref autónomo. Crear ramas = branch. Leer HEAD = symbolic-ref --quiet o rev-parse --verify --quiet. Mover el árbol = switch. Si el humano pide un compare-and-swap en una ref que no es HEAD: tres args, old-oid real, -m corto. Cero -d. Cero --stdin. Cero --no-deref. Cero cuarenta ceros salvo que pida crear.
Dos args escriben; tres args verifican
El man parte el contrato en dos formas (más -d y --stdin):
| Forma | Qué hace | Agente |
|---|---|---|
<ref> <new-oid> | Escribe. Sigue refs simbólicas. HEAD = mueve la rama actual | Cero |
<ref> <new-oid> <old-oid> | Igual, solo si el valor actual es <old-oid> | Solo si el humano nombra la ref y el SHA viejo |
40 0 o string vacío como <old-oid> | make sure that the ref you are creating does not exist | Cero autónomo |
-d <ref> [<old-oid>] | Borra tras verificar <old-oid> | Cero |
--no-deref | Pisa <ref> misma, no el destino del symlink simbólico | Cero |
--stdin / -z / --batch-updates | Transacción o lote desde stdin | Cero |
--create-reflog | Crea reflog aunque no tocara | Cero |
-m <reason> | Mensaje en el reflog (TAB + texto) | OK si ya vas a escribir |
Tres args es compare-and-swap. El man: stores the <new-oid> in the <ref> … after verifying that the current value of the <ref> matches <old-oid>. Si otro proceso ganó, fallas sin escribir. Eso es el único motivo para existir este comando frente a echo SHA > .git/refs/heads/feat.
Cuarenta ceros como <old-oid> no es “reset a empty”. Es “esta ref no debe existir”. Verificado: con refs/heads/feat ya creada, update-ref refs/heads/feat <HEAD> 0000…0000 → 128, cannot lock ref … reference already exists. Al revés, zeros + ref inexistente → 0 (crea).
-d deletes the named <ref> after verifying that it still contains <old-oid>. Sin old-oid también borra (verificado: 0). Un agente no borra refs. Para ramas usa branch -d solo si --merged; nunca -D.

--stdin es una transacción, no un atajo
Con --stdin el man acepta update / create / delete / verify y las variantes symref-*, más start / prepare / commit / abort. If all <ref>s can be locked with matching <old-oid>s simultaneously, all modifications are performed. Otherwise, no modifications are performed. Un lote mal armado no “aplica lo que pueda”: o todo o nada. --batch-updates allows individual updates to fail due to invalid or incorrect user input y reporta rejected; errores de I/O tiran el lote entero. -z usa NUL. start sin commit aborta y puede dejar locks. Cero stdin.
Verificado 2026-09-06 (Git 2.50.1 / Apple Git-155). Repo mínimo, dos commits:
git update-ref refs/heads/feat <HEAD>: 0.show-ref --headslistafeatymain.- Tres args con 40 ceros sobre
featexistente: 128, reference already exists. Con old-oid real: 0. -d refs/heads/feat(sin old-oid): 0. La ref desaparece.- Dos args
update-ref HEAD <commit-viejo>: 0.rev-parse HEADcambia;status -sbsigue## mainconM f. El árbol no viajó. printf 'create refs/heads/tmp %s\n' $oid | git update-ref --stdin: 0. Un agente no lo usa como API.
Nombres: check-ref-format --branch antes. feat corto en update-ref no es el path; el man espera <ref> (refs/heads/feat). No adivines el prefijo.
Por qué un agente no “actualiza la ref para ayudar”
Porcelain (branch, tag, switch, commit) ya escribe refs con reflog, hooks y locks. update-ref es el primitivo de debajo. Dos args sobre HEAD es un reset --soft sin ORIG_HEAD de cortesía y sin que el humano lo pida. --no-deref sobre HEAD pisa el archivo simbólico: HEAD deja de apuntar a refs/heads/main y pasa a ser un SHA suelto (detached disfrazado). El man: If --no-deref is given, <ref> itself is overwritten, rather than the result of following the symbolic pointers.
Logging: si core.logAllRefUpdates es true (heads/remotes/notes o pseudorefs) o ya existe $GIT_DIR/logs/<ref>, appende oldsha1 SP newsha1 SP committer. Con -m, TAB + mensaje. An update will fail (without changing <ref>) si no hay ident o no puede escribir el log. Un sandbox sin ident no se “arregla” con --create-reflog.
No leas ni escribas .git/HEAD, .git/refs/* ni packed-refs a mano. El man de show-ref existe precisamente para no parsear esos archivos. Tampoco force-push: mover la ref local no publica; git push --force es otro verbo y está vetado.

Qué sí / no corre
| Pregunta | Comando | Prohibido |
|---|---|---|
| ¿Existe la ref local? | show-ref --verify --quiet refs/heads/feat (0/1) | update-ref -d para “probar” |
| ¿SHA de HEAD? | rev-parse --verify --quiet HEAD | update-ref HEAD … |
| ¿A qué rama apunta HEAD? | symbolic-ref --quiet HEAD | --no-deref sobre HEAD |
| ¿Crear rama? | git branch feat | update-ref refs/heads/feat <oid> |
| ¿Entrar a la rama? | switch | dos args sobre HEAD |
| ¿CAS que el humano pidió? | git update-ref -m "…" refs/heads/feat <nuevo> <viejo> | sin <old-oid>; zeros; otra ref |
| ¿Borrar rama? | git branch -d si --merged | -d de update-ref; -D |
| ¿Refs en GitHub? | REST Get a reference (Contents read) | REST write; --stdin local “para sincronizar” |
FAQ
¿Puedo usarlo para “arreglar” un detached HEAD? No. Detached se sale con switch a una rama. update-ref HEAD <oid> en detached escribe el SHA en HEAD; no te reatacha.
¿--batch-updates hace el lote más seguro? No para un agente. Permite que parte del stdin aplique. El man reporta rejected. Eso es estado a medias. Cero.
¿Cuarenta ceros como <new-oid> borra? En el dialecto --stdin, Specify a zero <new-oid> to ensure the ref does not exist after the update. En la forma posicional, el borrado es -d. Ninguno es autónomo.
¿Es lo mismo que git branch -f? -f / -M en branch también mueven una ref existente. Están vetados en branch. update-ref es el mismo movimiento sin el porcelain. Peor, no mejor.
Checklist
- No invoques
git update-refsalvo que el humano nombre la ref, el SHA nuevo y el viejo. - Nunca
HEADcomo<ref>. Nunca--no-deref. Nunca-d. Nunca--stdin. - Nombres con check-ref-format
--branch; path completorefs/heads/…. - Confirma el valor actual con show-ref
--verifyantes del CAS. - No sustituyas reset, switch ni REST de GitHub.
- Si el ident o el reflog fallan, para. No inventes
--create-reflog.
Hoy: si tu agente “crea ramas” con update-ref, cámbialo a git branch <nombre>. Si mueve HEAD con dos args, es un reset blando no pedido: quítalo. El resto del flujo de refs está en el hub de construir agentes y, para convenciones de repo, en AGENTS.md.
Lecturas relacionadas
Sigue explorando Coding Agents y otras piezas para builders.

git pack-refs para coding agents: packed-refs, no el dump al LLM

git repack para coding agents: compactar packs, no housekeeping a pelo

git shortlog para coding agents: recuento por autor, no dump de log
