spacedriveapp/spacedrive · warning · Error

useSpacedriveClient must be used within SpacedriveProvider

Error message

useSpacedriveClient must be used within SpacedriveProvider

What it means

Returned inside sync_now's copy-job dispatch branch when the SdPath list built from operations.to_copy is empty — the code takes source_paths.first() to derive the MVP destination and errors when there is nothing to copy. Because the branch was entered expecting copies, this indicates an empty/degenerate operation set rather than a user-input problem.

Source

Thrown at packages/ts-client/src/hooks/useClient.tsx:48

export function SpacedriveProvider({ client, children }: SpacedriveProviderProps) {
	return (
		<QueryClientProvider client={queryClient}>
			<SpacedriveClientContext.Provider value={client}>
				{children}
			</SpacedriveClientContext.Provider>
		</QueryClientProvider>
	);
}

/**
 * Hook to access the Spacedrive client
 * Must be used within a SpacedriveProvider
 */
export function useSpacedriveClient(): SpacedriveClient {
	const client = useContext(SpacedriveClientContext);

	if (!client) {
		throw new Error("useSpacedriveClient must be used within SpacedriveProvider");
	}

	return client;
}

// Also export for direct use
export { useClient };
function useClient() {
	return useSpacedriveClient();
}

View on GitHub (pinned to 6dfeccf211)

Solutions

  1. Treat it as a no-op: if nothing changed, skip the copy-job dispatch instead of surfacing an error (the operations already told you to_copy is empty).
  2. Check the computed operation counts (copy_count/delete_count logged by sync_now) to confirm the diff is genuinely empty.
  3. File/track a code fix: guard `if source_paths.is_empty() { return Ok(None) }` before building FileCopyJob, since erroring on an empty batch is not actionable.

Example fix

// before
let destination = if let Some(first) = source_paths.first() {
    first.clone()
} else {
    return Err(anyhow::anyhow!("No paths to copy"));
};

// after (empty diff is a no-op, not an error)
let Some(first) = source_paths.first() else {
    return Ok(None); // nothing to copy
};
let destination = first.clone();
Defensive patterns

Strategy: fallback

Validate before calling

// Skip dispatch when the diff has no copies.
if operations.source_to_target.to_copy.is_empty()
    && operations.target_to_source.as_ref().map_or(true, |o| o.to_copy.is_empty())
{
    return Ok(None); // nothing to copy, nothing to dispatch
}

Try / catch

match dispatch_copy_job(&operations).await {
    Ok(h) => Ok(h),
    Err(e) if e.to_string() == "No paths to copy" => Ok(None), // empty diff = no-op
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: A sync calculation that produced zero copy operations while still entering the dispatch branch — e.g. trees already in sync (only deletes or no-ops), or a race where the operations were computed against stale state. The comment marks it as MVP behavior: destination is 'first entry's parent', which is undefined for an empty list.

Common situations: Repeated sync_now calls on an already-converged conduit; deletion-only diffs; edge case in operation calculation returning an empty to_copy alongside a non-empty aggregate copy_count path.

Related errors


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