diff --git a/crates/hammer-build/src/sandbox.rs b/crates/hammer-build/src/sandbox.rs
index a33e1f9b..efabadfa 100644
--- a/crates/hammer-build/src/sandbox.rs
+++ b/crates/hammer-build/src/sandbox.rs
@@ -209,13 +209,18 @@ impl Sandbox {
for d in &self.deps {
d.hash(&mut h);
}
- // Junto al store para compartir filesystem (hardlinks no cruzan fs). El store suele estar en
- // `/store`; ubicamos el merge en `/.dmerge/`. Derivamos del primer dep.
+ // DENTRO del store (el padre INMEDIATO de las deps), no en `/../`: los hardlinks NO
+ // cruzan filesystems, y el store puede ser un mount aparte (en el worker es un VOLUMEN
+ // bind-montado: `/opt/hammer/store` en /dev/sdb, `/opt/hammer` en /dev/sda1). Con
+ // `.parent().parent()` el merge caía en la raíz ⇒ `cp -al` cross-device fallaba ⇒ fallback al
+ // apilado directo ⇒ overflow del lowerdir en kio (5315B > 4096B) y build imposible. El padre
+ // inmediato de un dep ES el store, siempre mismo fs que las deps. `.dmerge` (dot-prefix) no
+ // matchea `-` ⇒ invisible para el store lookup y build-state, como `.times`.
let base = self
.deps
.first()
.and_then(|d| std::fs::canonicalize(d).ok())
- .and_then(|d| d.parent().and_then(|p| p.parent()).map(|p| p.to_path_buf()))
+ .and_then(|d| d.parent().map(|p| p.to_path_buf()))
.unwrap_or_else(|| PathBuf::from("."));
let merged = base.join(".dmerge").join(format!("{:x}", h.finish()));
let done = merged.join(".done");
diff --git a/scripts/farm/cosecha-cron.sh b/scripts/farm/cosecha-cron.sh
index 0c987362..a1312c6a 100755
--- a/scripts/farm/cosecha-cron.sh
+++ b/scripts/farm/cosecha-cron.sh
@@ -68,7 +68,7 @@ else
continue
fi
# 2. RECOGE: store sellado worker→laptop (CAS, merge seguro).
- if rsync -az -e "$SSH" "root@$ip:$REMOTE/store/" "$ROOT/store/" 2>&1 | tail -2; then
+ if rsync -az --exclude /.dmerge -e "$SSH" "root@$ip:$REMOTE/store/" "$ROOT/store/" 2>&1 | tail -2; then
echo " cosecha ✓"
else
echo " ⚠ cosecha falló — sigo"