granja: la cosecha llenaba el disco — rsync sin -H y bajando .dmerge
La bajada del store murio con "No space left on device" con 95 G libres y un store remoto de 25 G. Dos causas que se multiplican: - `rsync -a` NO preserva hardlinks (eso es `-H`). El store es CAS y .dmerge hardlinkea los artefactos, asi que los 25 G que mide `du` en el origen -que deduplica por inodo- se escriben en destino como copias enteras, una por enlace. - `.dmerge` es cache pura: hammer la reconstruye sola, no aporta un artefacto y es la parte mas pesada del arbol (132 G en el hub, 111,7 GiB exclusivos). Ademas el fallo a mitad deja directorios creados sin ficheros, que es un cache-hit envenenado: `mise` se cosecho asi, como nombre vacio. Se anade un guardian que barre los vacios al terminar la bajada y avisa de re-correr. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
1 archivos · +33 −2
- scripts/farm/farm-sync.sh +33 −2
diff --git a/scripts/farm/farm-sync.sh b/scripts/farm/farm-sync.sh
index c88ae4a0..a6ff3f1d 100755
--- a/scripts/farm/farm-sync.sh
+++ b/scripts/farm/farm-sync.sh
@@ -75,13 +75,44 @@ echo "==> 2. bajando artefactos sellados del worker"
# son inmutables y un artefacto superado no vuelve a ser vigente salvo que una receta retroceda—
# excluirlos es seguro; y si una receta retrocediera, su artefacto se reconstruye, que es barato.
LEDGER="work/store-gc-superados.txt"
+# ── ⚠ `-H` Y EXCLUIR `.dmerge`: SIN ESTO LA COSECHA LLENA EL DISCO ─────────────────────────────
+# Medido el 2026-09-01: esta bajada murió con `No space left on device (28)` escribiendo
+# `store/.dmerge/b8bc5ecc…/usr/share/locale/az/LC_MESSAGES/kio6.mo`, con 95 G libres y un store
+# remoto de 25 G. Dos causas que se multiplican:
+#
+# 1. **`rsync -a` NO preserva hardlinks** (eso es `-H`, y no está incluido en `-a`). El store es
+# CAS y `.dmerge` hardlinkea los artefactos: en el worker, 25 G medidos con `du` —que deduplica
+# por inodo— son MUCHOS más en el cable, porque cada enlace se transfiere y se escribe como una
+# copia entera. El tamaño que informa `du` del origen NO es el que hace falta en el destino.
+# 2. **`.dmerge` es CACHÉ PURA y no tiene por qué viajar.** Es la capa de merge que hammer
+# reconstruye sola; copiarla no aporta un solo artefacto y es la parte más pesada del árbol
+# (en el hub llegó a 132 G con 111,7 GiB exclusivos). Cosechar es traerse ARTEFACTOS.
+#
+# El fallo además es a mitad de camino: rsync limpia sus temporales al abortar, pero los directorios
+# que ya creó quedan — y un directorio de artefacto sin ficheros es EXACTAMENTE un cache-hit
+# envenenado (`mise` llegó así en esa pasada). Ver la regla 3 del CLAUDE.md.
+RSOPTS=(-azH --exclude '/.dmerge')
if [ -s "$LEDGER" ]; then
echo " (excluyendo $(wc -l < "$LEDGER") artefactos ya podados — ver la nota del bucle de churn)"
- rsync -az -e "$SSH" --exclude-from="$LEDGER" "$VPS:$REMOTE/store/" ./store/
+ rsync "${RSOPTS[@]}" -e "$SSH" --exclude-from="$LEDGER" "$VPS:$REMOTE/store/" ./store/
else
- rsync -az -e "$SSH" "$VPS:$REMOTE/store/" ./store/
+ rsync "${RSOPTS[@]}" -e "$SSH" "$VPS:$REMOTE/store/" ./store/
fi
+# ── GUARDIÁN: ningún artefacto cosechado puede llegar VACÍO ────────────────────────────────────
+# Un directorio sin ficheros no es un artefacto, es un nombre — y `hammer build` lo lee como
+# presencia y sella sin construir. Barrerlos acá es barato; detectarlos tres eslabones más abajo
+# (manifiesto → grafo → build) cuesta una campaña.
+VACIOS=0
+for d in ./store/*/; do
+ case "$d" in ./store/.dmerge/) continue ;; esac
+ [ -d "$d" ] || continue
+ if [ -z "$(find "$d" -mindepth 1 -print -quit 2>/dev/null)" ]; then
+ echo " ⚠ artefacto VACÍO cosechado, lo quito: $d"; rmdir "$d" 2>/dev/null; VACIOS=$((VACIOS+1))
+ fi
+done
+[ "$VACIOS" -gt 0 ] && echo " ($VACIOS vacíos barridos — la bajada quedó a medias, re-corré el sync)"
+
if [ "${2:-}" = "--no-promote" ]; then
echo "✓ store sincronizado (sin promote)"; exit 0
fi