siyuan-note/siyuan · error

destination [%s] is not empty

Error message

destination [%s] is not empty

What it means

During legacy iOS workspace migration, the destination <workspaceBaseDir>/siyuan must be empty before entries are moved into it. This error is returned when ReadDir succeeds but the directory already contains entries, to avoid overwriting or mixing with an existing workspace.

Source

Thrown at kernel/util/working_mobile.go:171

func migrateLegacyIOSWorkspace(workspaceBaseDir string) (migrated bool, err error) {
	if ContainerIOS != Container || !gulu.File.IsDir(workspaceBaseDir) {
		return false, nil
	}

	for _, name := range []string{"conf", "data", "temp"} {
		if !gulu.File.IsDir(filepath.Join(workspaceBaseDir, name)) {
			return false, nil
		}
	}

	defaultWorkspaceDir := filepath.Join(workspaceBaseDir, "siyuan")
	destinationEntries, readErr := os.ReadDir(defaultWorkspaceDir)
	if readErr != nil {
		return false, fmt.Errorf("read destination [%s] failed: %w", defaultWorkspaceDir, readErr)
	}
	if 0 < len(destinationEntries) {
		return false, fmt.Errorf("destination [%s] is not empty", defaultWorkspaceDir)
	}

	var moves []workspaceDirMove
	for _, name := range legacyIOSWorkspaceEntries {
		from := filepath.Join(workspaceBaseDir, name)
		if _, statErr := os.Lstat(from); statErr != nil {
			if errors.Is(statErr, fs.ErrNotExist) {
				continue
			}
			return false, fmt.Errorf("stat source [%s] failed: %w", from, statErr)
		}

		to := filepath.Join(defaultWorkspaceDir, name)
		if _, statErr := os.Lstat(to); statErr == nil {
			return false, fmt.Errorf("destination [%s] already exists", to)
		} else if !errors.Is(statErr, fs.ErrNotExist) {
			return false, fmt.Errorf("stat destination [%s] failed: %w", to, statErr)
		}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. If the default workspace already has data you want, keep it and manually move needed legacy files yourself instead of migrating
  2. If the default workspace content is disposable, empty <base>/siyuan and restart the app to rerun migration
  3. Check whether a previous migration half-completed (files present but legacy dirs remain); complete the move manually
  4. Ensure migration runs before first workspace initialization on fresh installs so the destination is still empty
Defensive patterns

Strategy: validation

Validate before calling

// Go: verify the destination is empty before starting migration
dest := filepath.Join(base, "siyuan")
entries, err := os.ReadDir(dest)
if err == nil && len(entries) > 0 {
    return fmt.Errorf("aborting migration: destination %s already has %d entries", dest, len(entries))
}

Try / catch

// Go
migrated, err := migrateLegacyIOSWorkspace(base)
if err != nil && strings.Contains(err.Error(), "is not empty") {
    // default workspace already initialized; skip migration and keep current data
    return nil // or prompt the user to resolve manually
}

Prevention

When it happens

Trigger: migrateLegacyIOSWorkspace (via initWorkspaceDirMobile) finds len(destinationEntries) > 0 in <base>/siyuan on a device where the default workspace was already initialized before legacy migration ran.

Common situations: User already opened the app once on the new version (creating conf/ etc. in siyuan/) before legacy data migration triggered; partial previous migration left files behind; user manually created content in the default workspace.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/559487e93d3ea6c2. Report an issue: GitHub.