You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Directory downloads should retry into a fresh staging directory #4805
sandbox_sync_down_file extracts each attempt into a TempDir in the destination parent and then moves the completed file into place. sandbox_sync_down_directory extracts directly into the final destination.
Because retry_sandbox_sync treats failed to extract tar archive from sandbox as retryable, a directory-download failure can retry into a destination that already contains a partially extracted tree. This can make otherwise recoverable retries fail on pre-existing entries and leaves the caller with a dirty destination.
In the linked incident, the destination was cleared before the first download, but the first internal extraction attempt failed. The Display-form error did not expose the underlying cause, so it was not possible to determine whether the failure was transient.
Request
For directory downloads, please stage each retry in a fresh TempDir in the destination parent, similar to sandbox_sync_down_file, and publish the completed tree only after extraction succeeds. This would make internal retries idempotent and allow downstream callers to avoid an extra outer retry.
Alternatively, please stop treating deterministic extraction failures such as pre-existing entries as retryable, or expose enough structured error detail for callers to distinguish transient extraction failures from deterministic ones.
The directory path needs the same staging treatment as the file path, but the staging dir has to be created in the destination's parent, not in TempDir — rename is only atomic within a filesystem, and /tmp will hand you EXDEV the first time the destination is on a mounted volume.
The swap is where it actually gets fiddly, since rename(staging, dest) fails with ENOTEMPTY when dest already exists:
// staging = dest.parent().join(format!(".{}.staging.{}", name, attempt))extract_tar(archive,&staging)?;// full extract, no partial destif dest.exists(){
fs::rename(&dest,&dest_old)?;// dest -> dest.old}
fs::rename(&staging,&dest)?;// staging -> destlet _ = fs::remove_dir_all(&dest_old);// best effort
Two things that bite if you skip them: the staging dir has to be per-attempt and removed on the failure path, otherwise the retry inherits the previous attempt's partial tree and you've just relocated the bug rather than fixed it. And on startup, sweep any leftover dest.old — if the process died between the two renames you'll otherwise leave a stale tree sitting next to the destination forever.
Separately, I'd narrow what retry_sandbox_sync considers retryable. A tar extraction failure is usually a truncated stream and worth another attempt; a checksum mismatch or a path-traversal rejection is deterministic and retrying it just burns the deadline.
Thanks - agreed. To clarify, the intent is tempfile::TempDir::new_in(destination_parent), matching the file path, so staging and final destination remain on the same filesystem and avoid EXDEV.
Per-attempt staging plus failure cleanup is also required, and narrowing retryable errors makes sense so deterministic checksum/path failures don’t consume the retry budget.
Fullsend currently works around this by staging each outer attempt in the destination parent; once OpenShell handles directory retries internally, we can remove that outer retry.
Activity
O96a commentedon Oct 21, 2026
The directory path needs the same staging treatment as the file path, but the staging dir has to be created in the destination's parent, not in
TempDir—renameis only atomic within a filesystem, and/tmpwill hand you EXDEV the first time the destination is on a mounted volume.The swap is where it actually gets fiddly, since
rename(staging, dest)fails with ENOTEMPTY whendestalready exists:Two things that bite if you skip them: the staging dir has to be per-attempt and removed on the failure path, otherwise the retry inherits the previous attempt's partial tree and you've just relocated the bug rather than fixed it. And on startup, sweep any leftover
dest.old— if the process died between the two renames you'll otherwise leave a stale tree sitting next to the destination forever.Separately, I'd narrow what
retry_sandbox_syncconsiders retryable. A tar extraction failure is usually a truncated stream and worth another attempt; a checksum mismatch or a path-traversal rejection is deterministic and retrying it just burns the deadline.Aliciapet11 commentedon Oct 22, 2026
Thanks - agreed. To clarify, the intent is tempfile::TempDir::new_in(destination_parent), matching the file path, so staging and final destination remain on the same filesystem and avoid EXDEV.
Per-attempt staging plus failure cleanup is also required, and narrowing retryable errors makes sense so deterministic checksum/path failures don’t consume the retry budget.
Fullsend currently works around this by staging each outer attempt in the destination parent; once OpenShell handles directory retries internally, we can remove that outer retry.