{"record":{"id":"0998d906bacb6fde","repo":"siyuan-note/siyuan","slug":"obsidian-vault-is-unreadable-normalize-vault-path","errorCode":null,"errorMessage":"Obsidian Vault is unreadable: normalize Vault path: %v","messagePattern":"Obsidian Vault is unreadable: normalize Vault path: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":564,"sourceCode":"\t\tret.ImportAssets[key] = asset\n\t\tret.Analysis.ImportableAssetCount++\n\t\tret.Analysis.ImportableAssetSize += asset.Source.Size\n\t}\n\tret.Analysis.UnreferencedFileCount = len(ret.ImportAssets) - len(ret.ReferencedAssets)\n\tif ret.Analysis.UnreferencedFileCount < 0 {\n\t\tret.Analysis.UnreferencedFileCount = 0\n\t}\n\tprogress(100, \"Analysis completed\")\n\treturn ret, nil\n}\n\nfunc validateObsidianVaultRoot(localPath string) (string, error) {\n\tif strings.TrimSpace(localPath) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"%w: path is empty\", errObsidianVaultUnreadable)\n\t}\n\tabs, err := filepath.Abs(filepath.Clean(localPath))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%w: normalize Vault path: %v\", errObsidianVaultUnreadable, err)\n\t}\n\tinfo, err := os.Lstat(abs)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%w: read Vault root: %v\", errObsidianVaultUnreadable, err)\n\t}\n\tif !info.IsDir() {\n\t\treturn \"\", errObsidianVaultNotDirectory\n\t}\n\tif info.Mode()&os.ModeSymlink != 0 || isObsidianResolvedLink(abs) {\n\t\treturn \"\", fmt.Errorf(\"%w: Vault root is a symbolic link or reparse point\", errObsidianVaultUnsafePath)\n\t}\n\tif util.IsSensitivePath(abs) {\n\t\treturn \"\", fmt.Errorf(\"%w: selected Vault path is sensitive\", errObsidianVaultUnsafePath)\n\t}\n\tworkspace, _ := filepath.Abs(filepath.Clean(util.WorkspaceDir))\n\tif sameObsidianPath(abs, workspace) || gulu.File.IsSubPath(workspace, abs) || gulu.File.IsSubPath(abs, workspace) {\n\t\treturn \"\", fmt.Errorf(\"%w: Vault root and SiYuan workspace contain each other\", errObsidianVaultUnsafePath)\n\t}","sourceCodeStart":546,"sourceCodeEnd":582,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/import_obsidian.go#L546-L582","documentation":"A wrapped variant of errObsidianVaultUnreadable (line 562-564): returned when filepath.Abs(filepath.Clean(localPath)) fails. filepath.Abs can fail on Windows when the current working directory drive cannot be determined, or in edge cases with path separators. The error wraps the unreadable sentinel with ': normalize Vault path: <err>'.","triggerScenarios":"POST /api/import/startObsidianVaultAnalysis with a path that causes filepath.Abs to error — rare, but possible on Windows when the process working directory is on a removed drive, or with paths containing NUL bytes or other characters invalid for the OS path API.","commonSituations":"Windows: the removable drive holding the CWD was ejected before the call; the path contains embedded NUL or control characters; an exotic OS-level path resolution failure.","solutions":["Pass an already-absolute path to avoid reliance on the process working directory.","On Windows, ensure the drive hosting the process CWD is still mounted.","Sanitize the path string to remove NUL bytes or control characters before submitting.","Retry from a stable working directory (e.g. the SiYuan workspace root)."],"exampleFix":"// before: relative path with no resolvable CWD\nlocalPath := \"./vault\" // CWD drive ejected on Windows\n// -> \"Obsidian Vault is unreadable: normalize Vault path: ...\"\n\n// after: pass absolute path\nlocalPath := \"D:/Vaults/MyVault\"","handlingStrategy":"validation","validationCode":"// Pass an absolute path and check it resolves\nabs, err := filepath.Abs(filepath.Clean(localPath))\nif err != nil { return fmt.Errorf(\"path cannot be resolved: %w\", err) }","typeGuard":null,"tryCatchPattern":"if _, err := model.StartObsidianVaultAnalysis(localPath); err != nil {\n    if errors.Is(err, errObsidianVaultUnreadable) && strings.Contains(err.Error(), \"normalize Vault path\") {\n        return errors.New(\"the path could not be resolved to an absolute path; provide a full path\")\n    }\n}","preventionTips":["Always pass an absolute path to avoid CWD dependency.","On Windows, ensure the CWD drive is mounted before the call.","Strip NUL/control characters from path strings."],"tags":["obsidian","import","validation","filesystem","windows","path-resolution"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}