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

  1. Rename one of the two conflicting files so their names differ by more than case (Obsidian will also warn about this)
  2. Update any wikilinks pointing at the old name after renaming
  3. Check the vault on a case-sensitive mount or via terminal to find all case-colliding pairs
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/a7d8a0049e1642fe. Report an issue: GitHub.