Guía10 min

Force push y amend en coding agents: por qué el remoto pierde commits

Resumen

git push --force puede borrar commits del remoto. --force-with-lease solo comprueba el valor esperado del ref y un fetch de fondo lo derrota. --amend reescribe el tip. Esta guía fija commit nuevo + push normal; nunca amend+force en un agente. Man pages git-push y git-commit.

GitHub
Un push forzado sobreescribe el tip remoto y deja commits huérfanos fuera de la rama

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.

Un coding agent que “arregla el historial” con git commit --amend y git push --force no está limpiando: está borrando el tip que ya vieron CI, reviewers y otros worktrees. El man page de git-push lo dice sin metáfora: --force desactiva las comprobaciones y puede hacer que el remoto pierda commits.

Esto no sustituye el cierre por PR con gh ni el aislamiento de worktrees. Aquí el contrato es uno: commit nuevo, push fast-forward, cero force. El curso instalar un agente asume un historial que todavía se puede leer.

Qué niega Git por default

git push se niega a actualizar un ref remoto que no es ancestro del ref local. Eso es el fast-forward. Si tu rama local reescribió commits (amend, rebase, reset), el remoto no es ancestro: el push normal falla. El fallo es la señal correcta.

--force apaga esa señal. Aplica a todos los refs que el comando empuja. Con push.default=matching o varios remote.*.push, un -f puede pisar refs que no son la rama actual — incluso refs locales que están detrás del remoto. El man page recomienda, si algún día un humano fuerza, un + delante de un refspec (git push origin +topic), no -f suelto.

Un agente no llega a ese “si algún día”. Prohíbe -f, --force y el + en AGENTS.md.

El tip remoto se mueve atrás; los commits que ya estaban publicados quedan fuera de la rama

--force-with-lease no es un permiso

--force-with-lease (y las formas =<refname> / =<refname>:<expect>) salta el fast-forward, pero solo si el valor actual del ref remoto es el que el agente espera. Si alguien más empujó encima, el lease caduca y el push falla.

Eso suena seguro. El mismo man page avisa que sin un <expect> explícito interactúa mal con cualquier cosa que haga git fetch en segundo plano: un cron, el editor, un IDE. La protección se derrota porque Git solo tiene el remote-tracking como heurística de “esto es lo que ya viste”. Un git fetch origin de un heartbeat actualiza esa heurística y el lease deja pasar un force sobre trabajo que el agente no integró.

--force-if-includes es un extra de --force-with-lease sin <expect>: comprueba que el tip del remote-tracking sea alcanzable desde el reflog local antes de forzar. Pasado solo, o con =<refname>:<expect>, es no-op. No convierte el force en un push normal.

Para un harness autónomo: lease y force-if-includes son la misma clase de incidente con más flags. No los uses.

--amend es un rewrite del tip

git commit --amend reemplaza el commit de HEAD. Nuevo árbol, mismos padres y autor (salvo --reset-author), mensaje del commit original como punto de partida. Equivale a git reset --soft HEAD^ + un commit sobre ORIG_HEAD. Sirve incluso para enmendar un merge.

El man page de git-commit pide entender las implicaciones de reescribir historia publicada y apunta a “RECOVERING FROM UPSTREAM REBASE” en git-rebase. Si el commit ya salió a origin, amend exige un push no fast-forward para que el remoto coincida. Ese es el par amend+force.

Corrección posterior = commit nuevo + git push normal. El PR muestra el fix encima, no un SHA que ya no existe.

AcciónLocalRemotoAgente
git commit (nuevo)Tip avanzaFast-forward
git commit --amend sin pushTip reescritoIntactaSolo si el commit nunca se publicó
git commit --amend + pushTip reescritoExige forceNunca
git push --forcePuede perder commitsNunca
git push --force-with-leaseForce si el lease coincideNunca
git push origin +ramaForce de un refspecNunca

El caso “el commit no se publicó”: git status limpio, no hay upstream, o git rev-parse @{u} falla. En un loop de PRs casi nunca es cierto: el primer push ya publicó.

Amend reescribe el SHA local; el push normal se niega; el force es el siguiente error

El servidor también puede decir no

receive.denyNonFastForwards=true hace que git-receive-pack niegue un update que no sea fast-forward, incluso si el push va forzado. Git lo activa al inicializar un repo compartido. Branch protection en GitHub es el mismo tipo de política, del otro lado: el agente no debe “probar -f por si acaso”. Si el push falla por non-fast-forward, el siguiente comando es un commit nuevo o un merge de origin/<rama>, no un flag.

Worktrees: cada uno tiene su HEAD. Force en el worktree A reescribe origin; el worktree B sigue apuntando a SHAs que el remoto ya no tiene. El siguiente git pull de B es un conflicto de historia, no de archivos. gitignore no entra aquí: el daño es de refs, no de untracked.

Receta cuando el push se niega

  1. git fetch origin
  2. git log --oneline HEAD..@{u} — qué hay en remoto que tú no tienes.
  3. Si son commits de otro (reviewer, CI bot, otro agente): git merge (o rebase solo si un humano lo pide y sin force después; en este repo, merge commit).
  4. Si el “atraso” es tu amend: git reflog, recupera el SHA publicado, commit nuevo encima de ese tip. No reescribas.
  5. git push sin flags. Si sigue fallando, reporta el bloqueador. No -f.

CI rojo no se arregla reescribiendo el commit que CI ya cacheó. Otro commit, mismo PR.

FAQ

¿El agente puede --amend el commit que acaba de crear, antes del primer push? Solo si @{u} no existe y nadie más tiene el SHA. En un cron que pushea cada guía, asume que ya se publicó. Más barato: commit nuevo siempre.

¿--no-verify junto al force? Otro incidente. Los hooks existen para parar exactamente este push. No los saltes.

¿Rebase de una rama ya pusheada? Mismo rewrite. Si la rama es pública, el follow-up es merge de origin/main, no rebase+force. Esta casa no hace force-push.

¿Tags? git push --force de tags mueve un nombre inmutable. Peor. El agente no toca tags.

Un git commit --amend -m wip && git push --force-with-lease es cómo un agente borra el review de hace diez minutos y deja al segundo worktree en un SHA huérfano. Si git push dice non-fast-forward, el siguiente comando no es -f: es git fetch y leer. El remoto no es un borrador.

Verificado 2026-09-03 contra git help push (--force, --force-with-lease, --force-if-includes) y git help commit (--amend). Git 2.50.1.