siyuan-note/siyuan · critical

move [%s] to [%s] failed: %w

Error message

move [%s] to [%s] failed: %w

What it means

The initial os.Rename of a legacy workspace entry from its old location to <base>/siyuan failed. On failure, migration attempts to rename back all already-completed moves and returns the joined error, so no data loss occurs but the workspace is not migrated.

Source

Thrown at kernel/util/working_mobile.go:204

			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...)...)
		}
		completed = append(completed, move)
	}

	for _, move := range completed {
		logging.LogInfof("moved legacy iOS workspace dir [from=%s, to=%s]", move.from, move.to)
	}
	return true, nil
}

func replaceLegacyIOSWorkspacePath(workspacePaths []string, workspaceBaseDir, defaultWorkspaceDir string) []string {
	for i, workspacePath := range workspacePaths {
		if filepath.Clean(workspacePath) == filepath.Clean(workspaceBaseDir) {
			workspacePaths[i] = defaultWorkspaceDir
		}
	}
	return DeduplicateWorkspacePaths(workspacePaths)
}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Read the wrapped %w error: EXDEV means use copy+delete; ENOENT means re-check which side is missing
  2. Ensure both source and destination live on the same volume/container path
  3. Close other SiYuan instances and retry the migration
  4. Free storage space if ENOSPC, then restart the app to rerun migration (it is idempotent per-entry: missing sources are skipped)
  5. If rollback errors were joined, manually reconcile the directories as described in the log
Defensive patterns

Strategy: retry

Validate before calling

// Go: pre-flight both endpoints of every move
for _, name := range legacyIOSWorkspaceEntries {
    if _, err := os.Lstat(filepath.Join(base, name)); err != nil { continue }
    if _, err := os.Lstat(filepath.Join(base, "siyuan", name)); err == nil {
        return fmt.Errorf("pre-flight: %s would collide", name)
    }
}

Try / catch

// Go
migrated, err := migrateLegacyIOSWorkspace(base)
if err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) && errors.Is(perr.Err, syscall.EXDEV) {
        // fall back to copy+delete migration across filesystems
        err = migrateByCopy(base)
    }
}

Prevention

When it happens

Trigger: migrateLegacyIOSWorkspace calls os.Rename(move.from, move.to) and it errors - source vanished, destination created concurrently, EXDEV across filesystems, or permission failure.

Common situations: Legacy dir and default dir on different volumes (cross-device rename); another process locking/holding files; disk full; the app killed mid-migration previously leaving inconsistent expectations.

Related errors


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