{"record":{"id":"6c39c915c0ed9c21","repo":"siyuan-note/siyuan","slug":"rollback-s-to-s-failed-w","errorCode":null,"errorMessage":"rollback [%s] to [%s] failed: %w","messagePattern":"rollback \\[(.+?)\\] to \\[(.+?)\\] failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/util/working_mobile.go","lineNumber":200,"sourceCode":"\t\t}\n\n\t\tto := filepath.Join(defaultWorkspaceDir, name)\n\t\tif _, statErr := os.Lstat(to); statErr == nil {\n\t\t\treturn false, fmt.Errorf(\"destination [%s] already exists\", to)\n\t\t} else if !errors.Is(statErr, fs.ErrNotExist) {\n\t\t\treturn false, fmt.Errorf(\"stat destination [%s] failed: %w\", to, statErr)\n\t\t}\n\t\tmoves = append(moves, workspaceDirMove{from: from, to: to})\n\t}\n\n\tvar completed []workspaceDirMove\n\tfor _, move := range moves {\n\t\tif renameErr := os.Rename(move.from, move.to); renameErr != nil {\n\t\t\tvar rollbackErrors []error\n\t\t\tfor i := len(completed) - 1; 0 <= i; i-- {\n\t\t\t\tcompletedMove := completed[i]\n\t\t\t\tif rollbackErr := os.Rename(completedMove.to, completedMove.from); rollbackErr != nil {\n\t\t\t\t\trollbackErrors = append(rollbackErrors, fmt.Errorf(\"rollback [%s] to [%s] failed: %w\",\n\t\t\t\t\t\tcompletedMove.to, completedMove.from, rollbackErr))\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false, errors.Join(append([]error{fmt.Errorf(\"move [%s] to [%s] failed: %w\", move.from, move.to, renameErr)}, rollbackErrors...)...)\n\t\t}\n\t\tcompleted = append(completed, move)\n\t}\n\n\tfor _, move := range completed {\n\t\tlogging.LogInfof(\"moved legacy iOS workspace dir [from=%s, to=%s]\", move.from, move.to)\n\t}\n\treturn true, nil\n}\n\nfunc replaceLegacyIOSWorkspacePath(workspacePaths []string, workspaceBaseDir, defaultWorkspaceDir string) []string {\n\tfor i, workspacePath := range workspacePaths {\n\t\tif filepath.Clean(workspacePath) == filepath.Clean(workspaceBaseDir) {\n\t\t\tworkspacePaths[i] = defaultWorkspaceDir","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/working_mobile.go#L182-L218","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (rename fails across volumes)\nos.Rename(move.from, move.to)\n// after: fall back to copy+remove for cross-filesystem moves\nif err := copyAll(move.from, move.to); err == nil { os.RemoveAll(move.from) }","handlingStrategy":"try-catch","validationCode":"// Go: verify same-filesystem rename is possible before migrating (avoid EXDEV)\nsameVol, _ := sameFilesystem(base, filepath.Join(base, \"siyuan\"))\nif !sameVol { return errors.New(\"cannot migrate across filesystems safely\") }","typeGuard":null,"tryCatchPattern":"// Go\nif err != nil {\n    var joined interface{ Unwrap() []error }\n    if errors.As(err, &joined) {\n        for _, e := range errors.Unwrap(err).(interface{ Errors() []error }).Errors() {\n            log.Printf(\"migration rollback component: %v\", e) // inspect each move/rollback failure\n        }\n    }\n    // manually reconcile directories per logged from/to pairs before retrying\n}","preventionTips":["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"],"tags":["go","filesystem","ios","migration","rollback","rename"],"backgroundTag":"file-move-rollback-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}