siyuan-note/siyuan · error
Vault paths differ only by letter case [%s] and [%s]
Error message
Vault paths differ only by letter case [%s] and [%s]
What it means
Obsidian allows two vault paths that differ only in letter case (e.g. `Notes.md` and `notes.md`); SiYuan's import normalizes paths to a case-insensitive key, and when two distinct files map to the same key the import is aborted. This prevents silent overwrites or duplicate blocks once files are converted into SiYuan's tree, which cannot represent both names on a case-insensitive filesystem.
Source
Thrown at kernel/model/import_obsidian.go:655
continue
}
}
if err = scanObsidianVaultFiles(ctx, vault, rel, abs); err != nil {
return err
}
continue
}
if !isObsidianImportableFileMode(entryInfo.Mode()) {
vault.Analysis.SkippedSpecialCount++
continue
}
ext := strings.ToLower(filepath.Ext(name))
isMD := ext == ".md"
file := &obsidianSourceFile{RelPath: rel, AbsPath: abs, Size: entryInfo.Size(), ModTime: entryInfo.ModTime(), IsMD: isMD}
key := obsidianPathKey(rel)
if existing := vault.Files[key]; existing != nil && existing.RelPath != rel {
return fmt.Errorf("Vault paths differ only by letter case [%s] and [%s]", existing.RelPath, rel)
}
vault.Files[key] = file
if !isMD {
finalName := util.AssetName(util.FilterUploadFileName(filepath.Base(rel)), ast.NewNodeID())
vault.Assets[key] = &obsidianAssetPlan{Source: file, FinalName: finalName}
}
}
return nil
}
func isObsidianImportableFileMode(mode fs.FileMode) bool {
return mode.IsRegular()
}
func isObsidianResolvedLink(p string) bool {
resolved, err := filepath.EvalSymlinks(p)
return err == nil && !sameObsidianPath(filepath.Clean(p), filepath.Clean(resolved))
}View on GitHub (pinned to 8641553a1f)
Solutions
- Rename one of the two conflicting files so their names differ by more than case (Obsidian will also warn about this)
- Update any wikilinks pointing at the old name after renaming
- Check the vault on a case-sensitive mount or via terminal to find all case-colliding pairs
- Re-run the import after the vault is clean
Example fix
// before: vault contains both notes/Setup.md notes/setup.md // after: rename one file notes/Setup.md notes/setup-guide.md
Defensive patterns
Strategy: validation
Validate before calling
function findCaseCollisions(files) {
const seen = new Map()
for (const f of files) {
const key = f.toLowerCase()
if (seen.has(key)) return [seen.get(key), f]
seen.set(key, f)
}
return null
} Prevention
- Audit the vault for names differing only by case (`find . | tr 'A-Z' 'a-z' | sort | uniq -d`)
- Rename duplicates in Obsidian before importing
- Be extra careful after moving vaults between macOS/Windows and Linux
When it happens
Trigger: scanObsidianVaultFiles finds a second file whose relative path lowercased equals an already-registered file's key but whose actual spelling differs, i.e. the vault genuinely contains both `Foo.md` and `foo.md` (or `Img.png`/`img.png`).
Common situations: Vaults moved between case-insensitive (macOS/Windows) and case-sensitive (Linux) filesystems; duplicate notes created with the same name differing in case; sync tools that merge such files into one directory.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- %w: Vault root and SiYuan workspace contain each other
- %w: read Vault config directory: %v
- read Vault directory [%s]: %w
- read Vault path [%s]: %w
- 346
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/a7d8a0049e1642fe.
Report an issue: GitHub.