siyuan-note/siyuan · error
read Vault directory [%s]: %w
Error message
read Vault directory [%s]: %w
What it means
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.
Source
Thrown at kernel/model/import_obsidian.go:603
if statErr != nil {
if os.IsNotExist(statErr) {
return "", errObsidianVaultConfigMissing
}
return "", fmt.Errorf("%w: read Vault config directory: %v", errObsidianVaultUnreadable, statErr)
}
if !configInfo.IsDir() || configInfo.Mode()&os.ModeSymlink != 0 || isObsidianResolvedLink(configPath) {
return "", errObsidianVaultConfigMissing
}
return abs, nil
}
func scanObsidianVaultFiles(ctx context.Context, vault *obsidianVaultContext, relDir, absDir string) error {
if err := ctx.Err(); err != nil {
return err
}
entries, err := os.ReadDir(absDir)
if err != nil {
return fmt.Errorf("read Vault directory [%s]: %w", relDir, err)
}
sort.Slice(entries, func(i, j int) bool { return strings.ToLower(entries[i].Name()) < strings.ToLower(entries[j].Name()) })
for _, entry := range entries {
if err = ctx.Err(); err != nil {
return err
}
name := entry.Name()
rel := path.Join(relDir, filepath.ToSlash(name))
abs := filepath.Join(absDir, name)
if strings.HasPrefix(name, ".") {
if relDir == "" && name == ".obsidian" {
continue
}
vault.Analysis.SkippedHiddenCount++
continue
}
entryInfo, infoErr := entry.Info()
if infoErr != nil {View on GitHub (pinned to 251596fc0d)
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.
Example fix
// before: nested dir not readable by SiYuan user // /vault/notes/private (mode 700, owned by other user) // after: make traversable chmod -R u+rX /vault && chown -R $(whoami) /vault
Defensive patterns
Strategy: validation
Validate before calling
// Walk the vault and confirm every subdir is readable before analysis.
func vaultFullyReadable(root string) error {
return filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
if err != nil { return err }
if d.IsDir() {
if _, e := os.ReadDir(p); e != nil { return fmt.Errorf("unreadable dir %s: %w", p, e) }
}
return nil
})
} Type guard
null
Try / catch
null
Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Obsidian Vault is unreadable: read Vault config directory: %
- resolve assets directory [%s] failed: %w
- resolve asset [%s] failed: %w
- resolve assets root [%s] failed: %w
- mkdir box conf dir failed: %w
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/6ffe76876f1b349c.
Report an issue: GitHub.