spacedriveapp/spacedrive · error

Source path is not local

Error message

Source path is not local

What it means

LocalMoveStrategy::execute (core/src/ops/files/copy/strategy.rs:89) calls SdPath::as_local_path() on the source, which returns None for every non-Physical SdPath variant (Cloud, Content, Sidecar) and for Physical paths whose device_slug is not the current device (not 'local', not this device's slug, not this device's UUID — see is_current_device in core/src/domain/addressing.rs:204). Move-by-rename only works on files this process can open locally, so a remote/cloud source is rejected immediately.

Source

Thrown at core/src/ops/files/copy/strategy.rs:89

	) -> Result<u64>;
}

/// 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() {

View on GitHub (pinned to 6dfeccf211)

Solutions

  1. Before dispatch, check `source.is_local()` and route non-local sources to the cross-device strategy (execute_push/pull) or download step instead of LocalMoveStrategy.
  2. If the file should be local, rebuild the SdPath for the current device: Physical { device_slug: "local", path } or the current device slug.
  3. Verify device identity is stable: the data dir (which holds the device identity) must not have been recreated between path creation and move.

Example fix

// before
let bytes = LocalMoveStrategy.execute(ctx, &source, &dest, false, None).await?;

// after
let strategy = if source.is_local() {
    &LocalMoveStrategy as &dyn CopyStrategy
} else {
    &cross_device_strategy
};
let bytes = strategy.execute(ctx, &source, &dest, false, None).await?;
Defensive patterns

Strategy: type-guard

Validate before calling

// route only local sources to local strategies
if !source.is_local() {
    anyhow::bail!("source {} is not on this device", source.display());
}

Type guard

// SdPath already exposes the guard: is_local() covers all four variants
pub fn requires_local_access(source: &SdPath) -> bool {
    source.is_local()
}

Try / catch

// catch around strategy dispatch and re-route instead of failing the job
match LocalMoveStrategy.execute(ctx, source, dest, verify, cb).await {
    Err(e) if e.to_string().contains("not local") => {
        cross_device_copy(ctx, source, dest).await
    }
    other => other,
}

Prevention

When it happens

Trigger: Dispatching a move where source is an SdPath::Cloud ('s3://...'), SdPath::Content ('content://<uuid>'), or SdPath::Physical belonging to a paired peer device (device slug of another machine). Also happens after the local device identity changes (fresh data dir), making previously-local slugs no longer match.

Common situations: Copy/move job receives a destination-relative or cloud path by mistake; multi-device library where a file pinned from another device is moved; device re-provisioned so get_current_device_slug() changed; UI constructing SdPath from a display URI like 'local://other-device/...' without resolving to the local device.

Related errors


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