siyuan-note/siyuan · critical
rollback [%s] to [%s] failed: %w
Error message
rollback [%s] to [%s] failed: %w
What it means
During the move loop of legacy iOS workspace migration, an os.Rename failed and rollback of previously completed moves also failed for one or more entries. The returned error joins the original move failure with all rollback failures, so the workspace may be left in a partially migrated, unrolled-back state.
Source
Thrown at kernel/util/working_mobile.go:200
}
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...)...)
}
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] = defaultWorkspaceDirView on GitHub (pinned to 8641553a1f)
Solutions
- Manually inspect both directories and finish or undo the moves by hand, matching the logged move list (from/to pairs)
- If rollback failures mention EXDEV (cross-device link), migrate via copy+delete instead of rename, or ensure both dirs share one volume
- Free device storage and retry; rename and rollback both need space for directory updates
- Back up the legacy entries before any manual intervention; on success, the migrated default workspace will be usable
- Restart the app afterwards - migration skips entries whose sources no longer exist, so a hand-completed state is stable
Example fix
// before (rename fails across volumes)
os.Rename(move.from, move.to)
// after: fall back to copy+remove for cross-filesystem moves
if err := copyAll(move.from, move.to); err == nil { os.RemoveAll(move.from) } Defensive patterns
Strategy: try-catch
Validate before calling
// Go: verify same-filesystem rename is possible before migrating (avoid EXDEV)
sameVol, _ := sameFilesystem(base, filepath.Join(base, "siyuan"))
if !sameVol { return errors.New("cannot migrate across filesystems safely") } Try / catch
// Go
if err != nil {
var joined interface{ Unwrap() []error }
if errors.As(err, &joined) {
for _, e := range errors.Unwrap(err).(interface{ Errors() []error }).Errors() {
log.Printf("migration rollback component: %v", e) // inspect each move/rollback failure
}
}
// manually reconcile directories per logged from/to pairs before retrying
} Prevention
- Keep legacy and default workspace directories on the same volume so rename is atomic
- Ensure adequate free space before migration
- Do not force-kill the app during first-launch migration
- After rollback failures, manually restore the listed from/to pairs before any retry
When it happens
Trigger: In migrateLegacyIOSWorkspace, os.Rename(completedMove.to, completedMove.from) errors while unwinding after a failed move - typically because the 'to' or 'from' path is no longer accessible mid-operation.
Common situations: Cross-filesystem rename on containers where legacy dir and default dir are on different volumes (rename fails with EXDEV); storage filled up mid-migration; sandbox revoking access between operations; process kill during migration leaving mixed state.
Related errors
- move [%s] to [%s] failed: %w
- read destination [%s] failed: %w
- destination [%s] is not empty
- stat source [%s] failed: %w
- destination [%s] already exists
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/6c39c915c0ed9c21.
Report an issue: GitHub.