git replay para coding agents: objects nuevos, refs intactas
Resumen
git replay es experimental: reescribe commits sobre otra base y no toca working tree, index ni refs. El stdout es input de git update-ref --stdin. Sin --onto/--advance sale 129. Cero pipe autónomo. Distinto de rebase y cherry-pick. 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 replay reescribe un rango de commits sobre otra base y no mueve nada. El man (git-replay(1); git-scm.com/docs/git-replay 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): EXPERIMENTAL: Replay commits on a new base, works with bare repos too. DESCRIPTION: Takes ranges of commits and replays them onto a new location. Leaves the working tree and the index untouched, and updates no references. El stdout está pensado como input de update-ref --stdin. THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
SYNOPSIS: (EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance <branch>) <revision-range>.... Sin --onto ni --advance falla. Verificado 2026-09-06: git replay → option --onto or --advance is mandatory, exit 129. --onto target sin rango → need some commits to replay, 128. --onto y --advance juntos → cannot be used together, 128. --advance con un SHA → argument to --advance must be a reference, 128.
No es rebase: rebase mueve HEAD, toca el árbol y (si no abortas) reescribe la rama. Aquí el checkout no cambia. No es cherry-pick: cherry-pick aplica al index y crea commits sobre HEAD. Replay imita cherry-pick solo en el shape de --advance: el stdout apunta la rama destino a los SHA nuevos; tú decides si corres update-ref. GitHub Docs Committing changes (/en/pull-requests/how-tos/commit-changes, HTTP 200, 2026-09-06) habla de la UI de PRs, no de este plumbing.
Contrato: no pipes el stdout a update-ref --stdin en autónomo. Pregunta = imprime las líneas update refs/heads/… NEW OLD y para. Cero --contained a ciegas. Cero replay de main. Cero --force después.
Qué hace (y qué no)
Dos modos, mutuamente excluyentes:
--onto <newbase>: Starting point at which to create the new commits. May be any valid commit, and not just an existing branch name. El stdout actualiza las ramas del rango (como git rebase --update-refs). Verificado: git replay --onto target main..mybranch imprime una línea update refs/heads/mybranch <NEW> <OLD> y sale 0. git show-ref sigue mostrando el OLD. El working tree sucio (M f.txt) no bloquea: el replay sale 0 y el porcelain sigue sucio.
--advance <branch>: must be a branch name. El stdout actualiza esa rama (cherry-pick de forma). Verificado: git replay --advance target main..mybranch imprime update refs/heads/target <NEW> <OLD>. Un SHA no es referencia: 128.
--contained solo con --onto. El man: rebase de un stack de ramas contenidas en el rango. Verificado: --contained --onto target main..tipbranch imprime tres update (branch1, branch2, tipbranch) y sale 0. --contained solo → 129 (falta --onto/--advance).
EXIT STATUS del man: 0 = replay sin conflicto; 1 = hay conflictos; otro = no pudo empezar. Un 1 no es “aplica igual”. No hay index a medio merge: el conflicto vive en el proceso, no en tu disco.
El output (man OUTPUT): líneas update refs/heads/branch1 ${NEW} ${OLD}. --advance siempre una ref; --onto una o más. Eso es el contrato de update-ref --stdin. Tres args CAS. El agente que “ayuda” pipeando acaba de mover ramas que el humano no pidió.
Bare repos: el man lo dice en el NAME. Sirve donde no hay working tree. Por eso no toca disco. Un agente en un worktree propio todavía no debe aplicar el update: el output es un plan, no un mutate.

Lo que el agente sí / no corre
| Quiero | Comando | Trampa |
|---|---|---|
| Plan de rebase de una rama | git replay --onto <base> <base>..<rama> | el stdout no mueve la rama |
| Plan tipo cherry-pick | --advance <rama-destino> <base>..<fuente> | destino tiene que ser una ref, no un SHA |
| Stack de ramas | --contained --onto <base> <base>..<tip> | sin --contained solo mueve el tip |
| Aplicar el plan | humano + update-ref --stdin | pipe autónomo = rewrite de refs |
| Rebase de verdad | rebase | replay no es el porcelain |
| Un commit sobre HEAD | cherry-pick | --advance no toca HEAD |
Prohibido en autónomo:
- Pipe a
git update-ref --stdin. El man lo sugiere; el agente no lo ejecuta. Tres args CAS viven en la guía de update-ref, no aquí. - Sin
--onto/--advance(129) y sin adivinarHEADoorigin/main. --ontoy--advancejuntos (128). Elige uno.--advancecon SHA / tree-ish suelto (128). Tiene que serrefs/heads/….--containedsobre un rango que incluyemaino ramas ajenas. Tresupdateno son “más información”: son tres refs candidatas a moverse.--all/--branches/--reflogdel commit limiting. El synopsis hereda el wall de opciones de rev-list. Un dump de historial no es un plan.- Encadenar con rebase
-i, force-push oreset. Replay no reescribe HEAD; el siguiente comando sí puede. - Tratar exit 1 como 0. Conflicto = aborta. No hay
rereremágico aquí.
El porcelain cubre “quiero que mi rama se mueva”. Este verbo existe para scripts y bare repos que necesitan commits nuevos como objects y un plan CAS. Un tick de agente pregunta qué SHA nuevos saldrían, no aplica el rewrite.
Receta (60 segundos)
Solo en un worktree propio, y sin pipe:
git replay --onto origin/main origin/main..HEAD
echo $? # 0 plan, 1 conflicto, 128/129 mal uso
Si sale 0, lee las líneas update refs/heads/…. Compara OLD con git rev-parse <rama>. Si no coinciden, alguien más movió la rama: el CAS de update-ref fallaría igual. No corras el pipe.
Un cherry-pick de forma, mismo cuidado:
git replay --advance destino origen..fuente
--advance destino exige que destino sea una rama. Verificado: un SHA de 40 hex → 128.
Stack: --contained --onto origin/main origin/main..tip. Vas a ver N líneas. N>1 no se aplica en autónomo. Enséñale el plan al humano.
Errores que delatan al agente
- Correr
git replaysin flags y “arreglar” el 129 con--onto HEAD. HEAD no es un plan. El onto es la base nueva, el rango es lo que se copia. - Pipear stdout a
update-ref --stdin“porque el man lo dice”. El man describe el formato. Aplicarlo reescribe refs. Eso es update-ref, y esa guía prohíbe--stdinautónomo. - Confundirlo con rebase. Después del replay,
git statusyHEADsiguen iguales. Si el agente “no ve el rebase”, es que no lo hubo. --advancecon el SHA de target. 128. La rama es el nombre, no el object.--containedpara “ver más ramas”. Acabas de emitir updates para todo el stack. Un humano que pedía el tip recibe tres CAS.- Ignorar el 1 de conflicto y reintentar con otro onto. El proceso no dejó estado a medias; el 1 es la respuesta.
- Tratarlo como estable. El man abre con EXPERIMENTAL. Un agente no lo pone en un cron ni en un hook.
Checklist
-
--ontoo--advance, nunca los dos. 129 sin ellos. - Hay un
<revision-range>explícito. 128 = need some commits. -
--advancerecibe una ref, no un SHA. - Exit 0 → imprime el plan. Cero pipe a update-ref.
- Exit 1 → conflicto, aborta. No hay index sucio de merge.
- Working tree / index / HEAD no cambian. Si cambiaron, corriste otro verbo.
-
--containedsolo si un humano pidió el stack. - Experimental: no es default de ningún workflow autónomo.
FAQ
¿Por qué no uso siempre git rebase? Porque rebase mueve HEAD y el árbol. Replay calcula commits nuevos y te da un plan CAS. En un bare repo no hay árbol que mover. En un agente, el plan se lee; el mutate lo hace un humano.
¿--onto vs --advance? Misma receta de commits, distinta ref en el stdout. --onto mueve las ramas del rango (como rebase --update-refs). --advance mueve la rama argumento (como cherry-pick sobre esa rama). El man EXAMPLES usa el mismo origin/main..mybranch en los dos.
¿El working tree sucio bloquea? No. Verificado: un M en disco, replay --onto sale 0 y el porcelain sigue sucio. Por eso no es un “rebase seguro”: tampoco te avisa como checkout. El sucio es irrelevante porque no se usa.
¿Puedo aplicarlo en un hook pre-push? No. Experimental + update-ref --stdin + posible stack --contained. Un hook que reescribe refs es force-push con extra pasos.
Más plumbing de Git para agentes en comparativas y decisiones. El curso corto: /curso/instalar-agente.

Lecturas relacionadas
Sigue explorando Coding Agents y otras piezas para builders.

git stripspace para coding agents: limpia el mensaje, no el parche

git interpret-trailers para coding agents: parse, no reescribir el mensaje

git patch-id para coding agents: huella del diff, no del SHA
