{"record":{"id":"6ffe76876f1b349c","repo":"siyuan-note/siyuan","slug":"read-vault-directory-s-w","errorCode":null,"errorMessage":"read Vault directory [%s]: %w","messagePattern":"read Vault directory \\[(.+?)\\]: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":603,"sourceCode":"\tif statErr != nil {\n\t\tif os.IsNotExist(statErr) {\n\t\t\treturn \"\", errObsidianVaultConfigMissing\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"%w: read Vault config directory: %v\", errObsidianVaultUnreadable, statErr)\n\t}\n\tif !configInfo.IsDir() || configInfo.Mode()&os.ModeSymlink != 0 || isObsidianResolvedLink(configPath) {\n\t\treturn \"\", errObsidianVaultConfigMissing\n\t}\n\treturn abs, nil\n}\n\nfunc scanObsidianVaultFiles(ctx context.Context, vault *obsidianVaultContext, relDir, absDir string) error {\n\tif err := ctx.Err(); err != nil {\n\t\treturn err\n\t}\n\tentries, err := os.ReadDir(absDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"read Vault directory [%s]: %w\", relDir, err)\n\t}\n\tsort.Slice(entries, func(i, j int) bool { return strings.ToLower(entries[i].Name()) < strings.ToLower(entries[j].Name()) })\n\tfor _, entry := range entries {\n\t\tif err = ctx.Err(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tname := entry.Name()\n\t\trel := path.Join(relDir, filepath.ToSlash(name))\n\t\tabs := filepath.Join(absDir, name)\n\t\tif strings.HasPrefix(name, \".\") {\n\t\t\tif relDir == \"\" && name == \".obsidian\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvault.Analysis.SkippedHiddenCount++\n\t\t\tcontinue\n\t\t}\n\t\tentryInfo, infoErr := entry.Info()\n\t\tif infoErr != nil {","sourceCodeStart":585,"sourceCodeEnd":621,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/import_obsidian.go#L585-L621","documentation":"Thrown inside scanObsidianVaultFiles when os.ReadDir(absDir) fails while walking the vault tree. Unlike the root-config error (780), this is a raw fmt.Errorf (not wrapped in an obsidianUserError sentinel), so the task detail layer falls through to the generic code 351 unless an outer handler re-wraps it. It aborts the recursive scan that builds the file/asset inventory used by every later analysis stage.","triggerScenarios":"Any subdirectory of the vault that ReadDir cannot enumerate: permission-denied on a subdir (EACCES), a path that was deleted between root validation and descent (ENOENT), a removed removable medium, or a directory whose mode bits forbid the running user. Because the scan walks every entry, a single unreadable nested folder fails the whole task.","commonSituations":"Vault contains a folder owned by another user with mode 700; vault on a USB/external drive unplugged mid-analysis; cloud-sync stubs (Dropbox/OneDrive 'online-only' placeholders) that error on ReadDir when offline; SELinux/AppArmor denying readdir to the SiYuan process.","solutions":["Identify the failing subdirectory from the wrapped error path ([relDir]) and fix its read/execute bits: chmod -R u+rX <vault>.","If only some subdirs are inaccessible and unneeded, exclude them from the vault or move them out before analysis.","For cloud 'online-only' folders, force a local download of the whole vault (or disable online-only for that folder) and retry.","Re-run StartObsidianVaultAnalysis after the filesystem is healthy; the task system auto-replaces a queued/analysing task."],"exampleFix":"// before: nested dir not readable by SiYuan user\n//   /vault/notes/private  (mode 700, owned by other user)\n\n// after: make traversable\nchmod -R u+rX /vault && chown -R $(whoami) /vault","handlingStrategy":"validation","validationCode":"// Walk the vault and confirm every subdir is readable before analysis.\nfunc vaultFullyReadable(root string) error {\n    return filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {\n        if err != nil { return err }\n        if d.IsDir() {\n            if _, e := os.ReadDir(p); e != nil { return fmt.Errorf(\"unreadable dir %s: %w\", p, e) }\n        }\n        return nil\n    })\n}","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Ensure uniform read+execute bits across the vault (chmod -R u+rX).","Disable 'online-only' for cloud-synced vault folders before import.","Keep vaults on a stable local filesystem during analysis."],"tags":["obsidian-import","filesystem","permissions","recursive-scan"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}