Fragmentos geométricos de colores que se recomponen sobre una línea ramificada, como un árbol de commits que recupera sus piezas perdidas

git reset --hard de tu agente: recupera lo que borró


Si tu agente ha ejecutado git reset --hard, git clean o git stash drop y tu trabajo ha desaparecido, lo que puedes recuperar depende de una sola cosa: si ese código llegó a entrar en la base de objetos de git (con un commit, un git add o un stash). Si entró, git reflog o git fsck te lo devuelven. Si nunca entró, git no puede hacer nada.

Primero, identifica el comando exacto que ejecutó el agente

El diagnóstico empieza en el historial de la sesión, no en git. Sube por la conversación hasta encontrar la llamada a la shell y copia el comando literal: no es lo mismo git reset --hard que git restore ., y cada uno destruye una cosa distinta. Fíjate también en si el agente hizo git add o algún commit antes, porque eso cambia por completo lo que puedes rescatar.

Esta tabla resume qué se pierde con cada comando y por dónde se recupera, según la documentación oficial de git:

Comando del agenteQué destruyeRecuperable con git si...
git reset --hard <commit>Cambios en archivos rastreados, el staging y la posición de la ramaEstaba en un commit (reflog) o pasó por git add (fsck)
git restore . o git checkout -- .Cambios no añadidos al staging en archivos rastreadosLo que estaba en el staging sigue en el índice; lo demás, no
git clean -fdArchivos y carpetas sin rastrearCasi nunca: esos archivos no estaban en git
git stash drop o git stash clearEntradas del stashSí, con fsck, mientras no se hayan purgado
git commit --amend, git rebaseLos commits anteriores dejan de estar en la ramaSí, con reflog

La documentación de git reset describe --hard así: sobrescribe archivos y directorios con la versión del commit, puede sobrescribir archivos sin rastrear y actualiza el índice para que no quede nada en el staging. Por eso es el caso más frecuente y el que más asusta.

Por qué /rewind no te lo devuelve en Claude Code

Los checkpoints de Claude Code solo registran lo que el agente cambia con sus herramientas de edición, no lo que hace la shell. La documentación de checkpointing lo dice de forma explícita: los archivos modificados por comandos Bash no se registran y esos cambios no se pueden deshacer con rewind. Un git reset --hard es un comando Bash, así que queda fuera.

Hay un matiz. Si el agente había editado esos mismos archivos con su herramienta de edición durante la sesión, el menú de /rewind (o pulsar Esc dos veces con el input vacío) puede ofrecerte Restore code en esos puntos. La documentación no describe este escenario concreto, así que trátalo como un intento extra después de hacer la copia de seguridad del siguiente apartado, no como garantía. Ten en cuenta también que, según esa misma página, las ediciones de subagentes en segundo plano no se restauran y que las instantáneas se borran por defecto unos 30 días después.

En Codex, Cursor o Copilot la lógica de fondo es parecida: los mecanismos de deshacer de cada herramienta están pensados para sus propias ediciones, y un comando de shell destructivo es territorio de git. Comprueba la documentación de tu herramienta antes de contar con ella.

Antes de recuperar nada: congela el repositorio

Lo primero es parar al agente y hacer una copia del directorio completo, .git incluido. Cada comando de git que ejecutes a ciegas, o que el agente ejecute intentando "arreglarlo", puede sobrescribir justo lo que quieres salvar. Interrumpe la tarea y no le pidas que lo resuelva él solo.

Estos comandos copian el proyecto al directorio padre y desactivan temporalmente la recolección automática de basura en ese repositorio:

# Copia completa, con .git, antes de tocar nada
cp -a . ../rescate-$(basename "$PWD")

# Evita que git gc --auto purgue objetos mientras recuperas
git config gc.auto 0

# Al terminar, vuelve al comportamiento por defecto
# git config --unset gc.auto

El motivo de desactivar gc.auto: según la documentación de git gc, algunos comandos habituales lanzan git gc de forma automática cuando el repositorio crece, y poner gc.auto a 0 desactiva esa heurística. Por defecto, gc solo poda objetos sueltos inalcanzables con más de dos semanas (gc.pruneExpire vale 2.weeks.ago), así que tienes margen, pero no infinito.

Commits perdidos: reflog y una rama de rescate

Si tu trabajo llegó a estar en un commit, aunque fuera local y de tipo WIP, está casi seguro en el reflog. El reflog registra cada vez que se actualizó la punta de una rama o de HEAD en tu repositorio local, y permite referirte a posiciones anteriores con sintaxis como HEAD@{2}.

Este bloque lista los últimos movimientos de HEAD, crea una rama nueva apuntando al estado anterior al reset y se cambia a ella para revisarla:

git reflog -n 20
# Busca la línea justo anterior a "reset: moving to ..."
# Supongamos que es HEAD@{1}
git branch rescate HEAD@{1}
git switch rescate
git log --oneline -5

Crear una rama en lugar de hacer otro git reset --hard te deja las dos versiones vivas para compararlas con git diff main rescate. Si el reset fue el último movimiento, la misma página de git reset recuerda que ORIG_HEAD guarda la punta anterior de la rama, así que git branch rescate ORIG_HEAD también sirve.

El plazo importa: por defecto, gc.reflogExpireUnreachable poda a los 30 días las entradas que ya no son alcanzables desde la rama, y gc.reflogExpire a los 90 días el resto.

Cambios que pasaron por git add: fsck --lost-found

Si no había commit pero sí un git add, el contenido existe como blob suelto en .git/objects, aunque ya nada apunte a él. Cada git add guarda una copia completa del archivo en ese momento, así que puede haber varias versiones del mismo archivo.

Según la documentación de git fsck, --lost-found escribe los objetos colgantes en .git/lost-found/commit/ o .git/lost-found/other/, y en el caso de los blobs escribe directamente su contenido en el archivo. El siguiente bloque los vuelca y busca cuáles contienen un texto que recuerdes de tu código:

git fsck --lost-found
ls -lt .git/lost-found/other/ | head

# Busca por un nombre de función o variable que sepas que escribiste
grep -l "calcularDescuento" .git/lost-found/other/*

Lo que recuperas es contenido sin nombre: los archivos se llaman como su hash, porque un blob de git no guarda la ruta. Tendrás que abrir cada candidato, identificar a qué archivo corresponde y copiarlo a su sitio. Es tedioso, pero en un cambio de una tarde suelen ser pocos ficheros.

Si el agente ejecutó git restore . en lugar de un reset, prueba antes algo más simple: git diff --cached te muestra lo que sigue en el índice, porque ese comando restaura el directorio de trabajo desde el staging sin vaciarlo.

Stash borrado: el comando de la documentación de git

Un stash es internamente un commit, así que un git stash drop o git stash clear deja ese commit inalcanzable pero presente. La documentación de git stash incluye esta receta para listar los stashes que siguen en el repositorio aunque ya no aparezcan en git stash list:

git fsck --unreachable |
grep commit | cut -d\  -f3 |
xargs git log --merges --no-walk --grep=WIP

Busca el que coincida por fecha y mensaje, y recupéralo en una rama con git branch rescate-stash <hash> o aplícalo con git stash apply <hash>. Si el agente le puso un mensaje propio al stash con git stash push -m, quita el --grep=WIP para no filtrarlo.

Lo que git no puede devolver y dónde buscar después

Los cambios que nunca pasaron por git add, y los archivos sin rastrear que borró git clean, no existen para git. La documentación de git clean lo deja claro: elimina archivos que no están bajo control de versiones. En este caso tus opciones están fuera de git:

  • Local History de VS Code: según la documentación de la interfaz de VS Code, cada vez que guardas un archivo en el editor se añade una entrada a la vista Timeline, y la acción Local History: Find Entry to Restore recupera archivos borrados. Solo tendrá lo que guardaste tú desde el editor, no lo que escribió el agente por la shell. Está activada por defecto con un límite de 50 entradas por archivo.
  • Pestañas abiertas con cambios sin guardar: si tenías el archivo abierto y modificado, no cierres esa pestaña; ese contenido puede vivir solo ahí.
  • Copias del sistema: Time Machine, historial de archivos de Windows o snapshots del sistema de archivos, si los tienes configurados.

Si nada de esto aplica, el trabajo está perdido. Asumirlo pronto te ahorra horas de búsqueda y te lleva a lo que sí controlas: que no se repita.

Que no vuelva a pasar: reglas ask y un punto de guardado por turno

La prevención tiene dos piezas: obligar al agente a pedir permiso antes de comandos git destructivos y asegurarte de que tu trabajo está en la base de objetos antes de cada turno. Una sin la otra se queda corta.

En Claude Code, este fragmento de .claude/settings.json hace que el agente tenga que pedirte aprobación para cualquier variante de esos comandos. Según la documentación de permisos, las reglas se evalúan en orden deny, ask y allow, gana la primera que coincide, y * funciona como comodín en cualquier posición:

{
  "permissions": {
    "ask": [
      "Bash(git reset *)",
      "Bash(git clean *)",
      "Bash(git restore *)",
      "Bash(git checkout *)",
      "Bash(git stash *)",
      "Bash(git push *)"
    ]
  }
}

Las reglas van sobre el subcomando completo y no sobre flags como --hard por una razón: la misma documentación advierte de que los patrones que intentan restringir argumentos son frágiles. Una regla sobre git push --force * no atrapa git push -f. Aun así, variantes como git -C ruta reset pueden escapar, así que trátalo como red, no como muro. Si prefieres bloquear del todo, mueve las líneas a deny.

La segunda pieza es un hábito. Antes de lanzar una tarea al agente, guarda un punto de recuperación:

git add -A && git commit -m "wip: antes del agente"

Si no quieres ensuciar el historial, basta con git add -A: no es tan cómodo de recuperar como un commit, pero deja tu trabajo como blobs que git fsck --lost-found encuentra. Como referencia práctica, no como dato, es el gesto más barato con mayor retorno de este artículo. Luego puedes reorganizar los commits WIP con git rebase -i antes de abrir la pull request.

Cuándo este procedimiento no aplica

Todo lo anterior asume que el agente trabajó sobre tu repositorio local y que el directorio .git sigue en tu disco. Hay casos en que no es así:

  • El agente corrió en un contenedor o sandbox desechable: si el repositorio vivía dentro y el contenedor ya no existe, el reflog y los objetos sueltos se fueron con él. Recupera desde lo último que se subió al remoto.
  • Force push al remoto: tu reflog local sigue teniendo la punta anterior y puedes rescatarla con una rama, pero restaurar el remoto afecta a otras personas. Coordínalo con tu equipo antes de volver a forzar nada.
  • Han pasado semanas: pasados los plazos por defecto de gc.pruneExpire y gc.reflogExpireUnreachable, y si gc llegó a ejecutarse, los objetos pueden haber desaparecido. Ejecuta git fsck --lost-found igualmente, pero no esperes milagros.
  • El agente trabajaba en un git worktree: el reflog de HEAD es propio de cada worktree, así que ejecuta git reflog dentro de ese worktree, no en el principal.

Para todo lo demás, el orden es siempre el mismo: localiza el comando exacto, copia el directorio, desactiva gc.auto y prueba primero reflog, luego fsck y, por último, las copias de fuera de git.

Compartir X LinkedIn