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
- Decide which copy wins: remove or rename the colliding entry inside <base>/siyuan, then restart to rerun migration
- If the existing destination entry is from a partial migration and incomplete, delete it so the legacy source is moved cleanly
- Back up both copies before deleting; SiYuan cannot merge them automatically
- 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
- After a failed migration, verify no partial moves landed in the default dir before retrying
- Back up both source and destination before manual cleanup
- Run migration atomically on first launch to avoid concurrent creation of colliding files
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
- read destination [%s] failed: %w
- destination [%s] is not empty
- stat source [%s] failed: %w
- stat destination [%s] failed: %w
- rollback [%s] to [%s] failed: %w
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/f847f2cfb2e7e80b.
Report an issue: GitHub.