macOS: notes with NFD-normalized filenames disappear from file explorer and search when edited — regression in 1.13.5/1.13.6 (watcher existence check compares raw NFD event name against NFC-normalized listing)

On macOS, editing any note whose filename is stored in Unicode NFD (decomposed) form removes that note from the file explorer, search, and the vault index, even though the file still exists on disk. Restarting Obsidian brings the note back (full rescan); editing it again makes it disappear again. 100% reproducible per file.

This is a regression: 1.13.4 is not affected; 1.13.6 is affected (verified by diffing the two app bundles, see analysis below). Other reports suggest it started in 1.13.5.

This hits vaults with Korean/Japanese/accented filenames especially hard: files created by Obsidian itself get NFC names (so most users never see this), but files migrated from other tools, created in Terminal, checked out via git, or dating back to HFS±era macOS are commonly NFD. In my vault (migrated from Logseq), essentially every Korean-named note is NFD, so every one of them vanishes on edit under 1.13.5+.

Steps to reproduce

  1. On macOS (default case-insensitive APFS), quit Obsidian and create a file with an NFD-form name inside a vault, e.g.:

    cd /path/to/vault
    python3 -c "import unicodedata; open(unicodedata.normalize('NFD','café') + '.md','w').write('hello')"
    
  2. Open Obsidian 1.13.5 or 1.13.6. The note café.md appears in the file explorer and search. :white_check_mark:

  3. Open the note and type a few characters (any change that triggers a save).

Did you follow the troubleshooting guide? Y

Expected result

The note stays in the file explorer and remains searchable.

Actual result

Within a couple of seconds of the save, the note disappears from the file explorer and search. The file is untouched on disk. Restarting Obsidian restores it; editing reproduces the disappearance every time.
I reproduced this with restricted mode on; the root cause is in the core desktop filesystem watcher (below), so plugins and themes are not involved.

Environment

Observed on macOS 26 / Apple Silicon, Obsidian 1.13.6, installer 1.13.4, restricted mode on. Currently rolled back to 1.13.4, where the bug does not occur.


Additional information

Root cause analysis (from the shipped code)

In the desktop filesystem adapter, reconcileFile(e, t) receives e = the raw watcher event path and t = the NFC-normalized vault path. On case-insensitive filesystems (this.insensitive, always true on default macOS APFS) it verifies the file still exists by reading the parent directory and searching for the basename.
1.13.6 (from obsidian-1.13.6.asar, minified):

r=this.getFullRealPath(e),this.insensitive?(o=this.path.dirname(r),
a=this.path.basename("/"!==e?e:this.basePath),          // needle: RAW event path
[4,this.fsPromises.readdir(o)]):[3,7];
case 5:return-1!==l.sent().map(function(e){return Dl(e).normalize("NFC")})  // listing: NFC-normalized
  .indexOf(a)?[3,7]:[4,this.reconcileDeletion(e,t,n)];

1.13.4 (from the installer’s bundled obsidian.asar, minified):

r=this.getFullRealPath(e),this.insensitive?(o=this.path.dirname(r),
a=this.path.basename(t),                                 // needle: NFC-normalized path
[4,this.fsPromises.readdir(o)]):[3,7];
case 5:return-1!==l.sent().map(function(e){return Sl(e).normalize("NFC")})
  .indexOf(a)?[3,7]:[4,this.reconcileDeletion(e,t,n)];

Dl/Sl are the same whitespace-cleanup helper in both versions; the only functional change is the needle source: basename(t) (normalized) → basename(e) (raw).
On macOS, fs.watch (FSEvents) reports filenames in their on-disk byte form — NFD for NFD-named files. So in 1.13.5/1.13.6 the needle is an NFD string, the directory listing has been normalized to NFC, indexOf can never match, and reconcileDeletion() is invoked for a file that exists. In 1.13.4 both sides are NFC and the check passes.

Standalone repro of the failing comparison (no Obsidian needed)

// node repro.js /path/to/some/dir   — macOS only
const fs = require('fs');
const dir = process.argv[2] || '.';
const nfd = dir + '/' + 'café'.normalize('NFD') + '.md';
fs.writeFileSync(nfd, 'probe\n');
fs.watch(dir, { encoding: 'utf8' }, (ev, fn) => {
  if (!fn || !fn.normalize('NFC').includes('café')) return;
  const list = fs.readdirSync(dir).map(x => x.normalize('NFC'));
  console.log('event filename form :', fn === fn.normalize('NFC') ? 'NFC' : 'NFD');
  console.log('1.13.6-style check  :', list.includes(fn) ? 'found' : 'MISS -> reconcileDeletion');
  console.log('1.13.4-style check  :', list.includes(fn.normalize('NFC')) ? 'found' : 'MISS');
});
// simulate Obsidian saving through its NFC-canonical path
setTimeout(() => fs.appendFileSync(dir + '/' + 'café'.normalize('NFC') + '.md', 'edit\n'), 500);
setTimeout(() => { fs.unlinkSync(nfd); process.exit(0); }, 2500);

Output on macOS (APFS resolves the NFC path to the existing NFD file; the event reports the on-disk NFD name):

event filename form : NFD
1.13.6-style check  : MISS -> reconcileDeletion
1.13.4-style check  : found

Suggested fix

Normalize the needle the same way as the listing — e.g. keep the new base-path guard but derive the needle from the normalized path (basename(t), as in 1.13.4), or apply the same Dl(...).normalize("NFC") to the needle before the indexOf.

Workaround for affected users

Quit Obsidian, delete the downloaded update (~/Library/Application Support/obsidian/obsidian-1.13.*.asar) to fall back to the installer’s 1.13.4, and disable automatic updates until this is fixed. Confirmed to stop the disappearances in my vault (thousands of NFD-named notes).

Same here. Existing iCloud vault with many Korean filenames. Reproduced exactly on 1.13.6.
I use Korean.

we are reviewing this

will be fixed 1.13.7.

Independent confirmation on a fully updated installation, plus a directory-level variant:

System info

  • Obsidian 1.13.6 / installer 1.13.6
  • macOS 26.5.2, Apple Silicon
  • Built-in Obsidian Sandbox vault
  • Default theme, 0 CSS snippets, Restricted mode on

I reproduced the per-file disappearance. I also found that updating only the mtime of an NFD-normalized parent directory makes Obsidian falsely delete the directory and all of its indexed children.

Directory-level repro

  1. Create an NFD-normalized directory 한글 폴더 containing an NFD-normalized file 한글 파일.md.
  2. Reload the Sandbox vault so both are indexed.
  3. Update only the directory mtime externally with os.utime() or touch.

Observed vault events:

raw    한글 폴더
raw    한글 폴더
delete 한글 폴더/한글 파일.md
delete 한글 폴더

After the false deletes:

app.vault.getAbstractFileByPath("한글 폴더")                   -> null
app.vault.getAbstractFileByPath("한글 폴더/한글 파일.md")    -> null
await app.vault.adapter.exists("한글 폴더")                  -> true
await app.vault.adapter.exists("한글 폴더/한글 파일.md")     -> true
await app.vault.adapter.list("한글 폴더")                    -> ["한글 폴더/한글 파일.md"]

This confirms that the false deletion can cascade from an NFD directory to all children. No data is deleted, and reloading restores both the directory and file. It also explains the real-world symptom where editing one note made its entire category folder disappear until reload.

Thanks for tracking this. This directory-level case is reproducible with the 1.13.6 installer too and may be useful when verifying the 1.13.7 fix.