spacedriveapp/spacedrive · error
Destination path is not local
Error message
Destination path is not local
What it means
LocalMoveStrategy::execute (core/src/ops/files/copy/strategy.rs:92) requires the destination SdPath to be local too: as_local_path() returns None for Cloud/Content/Sidecar variants and for Physical paths on another device. A move renames within one filesystem, so a destination the daemon cannot write locally is rejected before any I/O.
Source
Thrown at core/src/ops/files/copy/strategy.rs:92
/// Strategy for an atomic move on the same volume
pub struct LocalMoveStrategy;
#[async_trait]
impl CopyStrategy for LocalMoveStrategy {
async fn execute<'a>(
&self,
ctx: &JobContext<'a>,
source: &SdPath,
destination: &SdPath,
verify_checksum: bool,
progress_callback: Option<&ProgressCallback<'a>>,
) -> Result<u64> {
let source_path = source
.as_local_path()
.ok_or_else(|| anyhow::anyhow!("Source path is not local"))?;
let dest_path = destination
.as_local_path()
.ok_or_else(|| anyhow::anyhow!("Destination path is not local"))?;
// Read size before rename since source path becomes invalid after move.
let metadata = fs::metadata(source_path).await?;
let size = if metadata.is_file() {
metadata.len()
} else {
get_path_size(source_path).await?
};
// Send initial progress event so UI shows 0% before the instant rename.
if let Some(callback) = progress_callback {
callback(0, size);
}
if let Some(parent) = dest_path.parent() {
fs::create_dir_all(parent).await?;
}
View on GitHub (pinned to 6dfeccf211)
Solutions
- Gate strategy selection on both ends: `if source.is_local() && destination.is_local()` before choosing LocalMoveStrategy.
- For cross-device destinations, use the cross-device transfer path (destination carries the peer's device_slug, execute_push handles it).
- Construct destination paths via the same helper that stamps the current device slug, never from raw display URIs of other devices.
Example fix
// before
if source.is_local() {
LocalMoveStrategy.execute(ctx, source, destination, verify, cb).await
}
// after
if source.is_local() && destination.is_local() {
LocalMoveStrategy.execute(ctx, source, destination, verify, cb).await
} else {
CrossDeviceCopyStrategy.execute(ctx, source, destination, verify, cb).await
} Defensive patterns
Strategy: type-guard
Validate before calling
// both ends must be local for a rename-based move
if !(source.is_local() && destination.is_local()) {
anyhow::bail!("local move requires local source and destination");
} Type guard
fn is_local_move_candidate(source: &SdPath, destination: &SdPath) -> bool {
source.is_local() && destination.is_local()
} Try / catch
// re-route on the 'not local' signal rather than aborting the user's move
let result = LocalMoveStrategy.execute(ctx, source, dest, verify, cb).await;
if result.as_ref().err().map(|e| e.to_string().contains("not local")).unwrap_or(false) {
return CrossDeviceCopyStrategy.execute(ctx, source, dest, verify, cb).await;
}
result Prevention
- Centralize strategy selection in one router that switches on SdPath kind for both ends.
- Never construct destination paths from another device's display URI.
- Cover all SdPath variant pairs in router unit tests.
When it happens
Trigger: Moving a local file to a Cloud path ('s3://bucket/...'), a Content-addressed path, or a Physical path whose device slug names a peer device instead of 'local'/this device.
Common situations: Drag-and-drop onto a cloud location in the UI but the job router picked the local move strategy; destination URI built from the peer device's display string; destination constructed with a stale device slug after re-pairing.
Related errors
- Source path is not local
- Destination must have a device slug for cross-device transfe
- Checksum verification failed
- Source must be local path for PUSH operation
- Source must be a physical path for PULL operation
AI-assisted analysis of spacedriveapp/spacedrive@6dfeccf211 (2026-08-16).
Data as JSON: /api/errors/02513ec82cfc97a6.
Report an issue: GitHub.