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

  1. Gate strategy selection on both ends: `if source.is_local() && destination.is_local()` before choosing LocalMoveStrategy.
  2. For cross-device destinations, use the cross-device transfer path (destination carries the peer's device_slug, execute_push handles it).
  3. 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

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


AI-assisted analysis of spacedriveapp/spacedrive@6dfeccf211 (2026-08-16). Data as JSON: /api/errors/02513ec82cfc97a6. Report an issue: GitHub.