siyuan-note/siyuan · error

destination [%s] already exists

Error message

destination [%s] already exists

What it means

For each legacy entry that exists in the source, migration checks that the corresponding path inside <base>/siyuan does not already exist (os.Lstat succeeds means it exists). This error prevents silently overwriting an existing file or directory in the destination workspace.

Source

Thrown at kernel/util/working_mobile.go:186

		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)
		}
		moves = append(moves, workspaceDirMove{from: from, to: to})
	}

	var completed []workspaceDirMove
	for _, move := range moves {
		if renameErr := os.Rename(move.from, move.to); renameErr != nil {
			var rollbackErrors []error
			for i := len(completed) - 1; 0 <= i; i-- {
				completedMove := completed[i]
				if rollbackErr := os.Rename(completedMove.to, completedMove.from); rollbackErr != nil {
					rollbackErrors = append(rollbackErrors, fmt.Errorf("rollback [%s] to [%s] failed: %w",
						completedMove.to, completedMove.from, rollbackErr))
				}
			}
			return false, errors.Join(append([]error{fmt.Errorf("move [%s] to [%s] failed: %w", move.from, move.to, renameErr)}, rollbackErrors...)...)

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Decide which copy wins: remove or rename the colliding entry inside <base>/siyuan, then restart to rerun migration
  2. If the existing destination entry is from a partial migration and incomplete, delete it so the legacy source is moved cleanly
  3. Back up both copies before deleting; SiYuan cannot merge them automatically
  4. Keep legacy data untouched until migration reports success - the source is only renamed, not deleted
Defensive patterns

Strategy: validation

Validate before calling

// Go: detect name collisions ahead of migration
for _, name := range legacyIOSWorkspaceEntries {
    if _, err := os.Lstat(filepath.Join(base, "siyuan", name)); err == nil {
        return fmt.Errorf("pre-check: %s already exists in destination; resolve before migrating", name)
    }
}

Try / catch

// Go
if err != nil && strings.Contains(err.Error(), "already exists") {
    // resolve manually: remove/rename the colliding destination entry, then retry migration
    return err
}

Prevention

When it happens

Trigger: migrateLegacyIOSWorkspace finds an entry named in legacyIOSWorkspaceEntries both in the legacy location and already present inside <base>/siyuan.

Common situations: A previous partial migration copied some entries but crashed before finishing; the app recreated default files (e.g. conf) that collide with legacy names; the user copied data manually.

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/f847f2cfb2e7e80b. Report an issue: GitHub.