Tutorials 24 min read

files.md or Files.md? The Case-Sensitivity Trap That Corrupts a Note Vault Silently

MMNMNOTE
case-sensitivityapfsntfsext4filename-conventiongitnote-vaultcross-platform

Two silent-corruption traps hit a cross-OS note vault the day it crosses a Mac, a Windows laptop, and a Linux server. The louder is line endings, spelled out in the companion to this piece. The quieter is filename case — files.md and Files.md, the same eight bytes, resolving to one file on macOS and Windows and two on Linux.

Read this together with Line Endings in Your Notes; as a pair, they are the two silent-corruption traps of note-vault-on-git-across-OSes — the two things nobody warns you about when you start versioning notes across a Mac and a Linux server. Neither is anyone's fault. They are compatibility surfaces the vault owner learns to live with.

Apple's own APFS FAQ names the shape in one sentence: "APFS, like HFS+, is case-sensitive on iOS and is available in case-sensitive and case-insensitive variants on macOS, with case-insensitive being the default" 1. Microsoft's Windows Subsystem for Linux docs are equally blunt: "Windows file system treats file and directory names as case-insensitive. FOO.txt and foo.txt will be treated as equivalent files. Linux file system treats file and directory names as case-sensitive. FOO.txt and foo.txt will be treated as distinct files" 2. Three operating systems, three defaults, one plain-Markdown vault trying to live on all of them.

What "case-insensitive" actually means — three OSes, three defaults

A file system is case-insensitive when it stores the case you typed but ignores it during lookup, and case-sensitive when the eight bytes of a filename are the only key it consults. Apple's APFS is case-insensitive by default on macOS, case-sensitive on iOS. Windows treats NTFS as case-insensitive at the Win32 layer. Linux ext4 is case-sensitive out of the box.

The macOS side is documented in Apple's APFS FAQ: the default variant on the shipping Mac is case-insensitive, and the same file system is case-sensitive on iOS 1. A vault owner who deliberately picks APFS (Case-sensitive) at install time gets Linux-like behavior on their Mac and does not hit this trap at all. Everyone who accepted the installer default does.

The Windows side is documented in Microsoft's Win32 file-I/O reference under Naming Files, Paths, and Namespaces: "Do not assume case sensitivity. For example, consider the names OSCAR, Oscar, and oscar to be the same, even though some file systems (such as a POSIX-compliant file system) may consider them as different. Note that NTFS supports POSIX semantics for case sensitivity but this is not the default behavior" 3. The NTFS kernel is case-sensitive; the Win32 layer that every ordinary Windows application, editor, and PowerShell command talks to is not.

The Linux side is documented in kernel.org's ext4 admin guide, which describes the opt-in casefold feature — the fact that it needs describing tells you the default: "in order to enable case-insensitive directories, the filesystem must have the casefold feature, which stores the filesystem-wide encoding model used" 4. Case-insensitive lookup on ext4 is a per-directory attribute a root user has to toggle. Every unaltered ext4 vault is case-sensitive.

Adam Johnson names both sides plainly on his personal blog: "The Linux filesystem ext4 is case sensitive, by default. It allows both Example.txt and example.txt to co-exist" 5. And on the Mac: "macOS' default APFS is case insensitive, by default. It interprets the filenames Example.txt and example.txt and eXaMpLe.TxT as all referring to the same file" 5.

One command, run in the vault directory, tells you which side you are on:

# One-command detection: does this filesystem treat cases as distinct?
touch A && touch a && ls
# macOS default / Windows: one file. Linux ext4: two files.

Two lines of output, and the vault has told you what world it lives in.

Why the collision looks like corruption but isn't

The failure mode is a wall of ghost duplicates. Pull the vault on a Linux box that already had it on macOS, and both Files.md and files.md show up in the file listing. Nothing is corrupted. Nothing is lost. What happened is that decidability drifted — the same name now points to two files, not one, and git noticed.

The .NET Core team hit exactly this pattern on their DasBlog Core repository, and Scott Hanselman — then a partner program manager at Microsoft — wrote it up on his personal blog with the incident detail intact: "Turns out that six months ago one of us introduced another folder with the name dasblog while the original was DasBlog. When we checked them on Mac or Windows the files ended up in merged into one folder, but on Linux they were/are two, so the build fails" 6. The Windows and Mac developers saw nothing wrong. The CI build on Linux collapsed.

The same class of trap has a well-known analogue outside the note-vault world. Rust's Cargo has it on record as issue #11962, a ticket reporting "A project created on Windows, where the user decided to use cargo.toml will not run on Linux" 7. A Windows developer renamed Cargo.toml to cargo.toml, committed it, and the build worked on their box; the same commit failed to find its manifest on Linux CI. The bug is not Rust-specific; it is filesystem-shape-specific, and it hits every plain-text repository that crosses OSes without a naming discipline.

A real Obsidian vault owner — Moonbase59 — hit the note-flavored version on the Obsidian community forum in a thread that has been active since August 2021: "Files with perfectly legal characters and different 'casing' aren't handled consistently in Obsidian" and "I regard this as a bug. Differently 'cased' filenames are different, at least on Operating Systems like Linux" 8. The vendor's behavior is downstream of the OS. Obsidian is not doing something wrong; the vault owner is running into a decidability surface neither the app nor the OS advertises.

Before you rename anything, scan the vault for existing collisions. Two POSIX commands do the whole audit:

# Scan for case-only collisions across the whole vault (from the vault root):
find . -type f -exec basename {} \; | sort -f | uniq -di
# -f sorts case-insensitively; -di reports only duplicates, in lowercase.

# The same scan against what git has actually stored:
git ls-files | sort -f | uniq -di
# case-collisions in the git index; runs on any OS with git-bash or WSL.

Zero output means the vault has no collisions yet. Any output is the list of files that need renaming before the vault crosses OSes again.

Pick one convention: lowercase-kebab-ASCII

The safe convention is lowercase-kebab-ASCII: every filename lowercase, words joined with hyphens, characters restricted to a-z, 0-9, and hyphen. It is the intersection of what every filesystem, every git remote, and every URL accepts without transformation. Once you adopt it, case stops being a category the note vault has to reason about.

The intersection matters because there are four different consumers of a filename in a plain-text note vault, and each one has a slightly different tolerance. The filesystem accepts either case, in three different ways per OS. Git records exactly the bytes it sees, but its core.ignoreCase behavior differs by OS. A remote URL — the moment a note becomes a link — is served over HTTP, where the path segment after the host is defined as case-sensitive by RFC. And a modern editor's fuzzy-open dialog scores case-sensitively against your typed prefix. Only lowercase-kebab-ASCII passes all four without transformation.

The convention is not a taste preference; it is a compatibility contract with the machines that will read the files. Steph Ango's principle — apps are ephemeral, files have a chance to last — assumes the files are ones every future reader can open. A Files.md that resolves to files.md on one machine and to nothing on the next is a file that has already broken its own promise.

The sibling piece to this one, How to Name Your Note Files, covers the inside-a-team naming convention (kebab-case, date prefixes, semantic hyphens). This piece covers the across-OSes case-decidability surface — the specific reason the convention has to be lowercase. Read them together as the two-file discipline: how you shape the name, and why the shape stops surprising you.

Renaming a file to lowercase — the git recipe

On macOS or Windows, renaming Notes.md to notes.md in Finder or Explorer is a no-op — the file system already treated them as the same file. The rename that actually reaches git is one command: git mv Notes.md notes.md. When git refuses, a two-step temp-name rename gets past the case-preserving refusal without touching core.ignoreCase.

Adam Johnson's post gives the single-step form and the single reason it works: git's -f/--force flag on git mv tells git to accept the rename even when the OS reports the destination "exists" 5. When the single-step still fails — which happens on directories more often than on files — Hanselman's two-step recipe is the standard workaround:

# Single-step lowercase rename (Adam Johnson's recipe):
git mv Notes.md notes.md
git commit -m "Rename Notes.md to notes.md (lowercase convention)"

# Two-step temp-name rename for stubborn cases (Hanselman's recipe):
git mv Notes.md notes-tmp.md
git mv notes-tmp.md notes.md
git commit -m "Rename Notes.md to notes.md (via temp)"

Hanselman names the exact shape of the fallback in his post: "If you're renaming a directory, you'll do a two stage rename with a temp name" 6. The commands themselves — git mv foo foo2 && git mv foo2 FOO && git commit -m "changed case of dir" — are the verbatim recipe he ran to fix the DasBlog Core incident.

The mechanic underneath is a git config value called core.ignoreCase, and the git project's own reference documents what it does: "Internal variable which enables various workarounds to enable Git to work better on filesystems that are not case sensitive, like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing finds makefile when Git expects Makefile, Git will assume it is really the same file, and continue to remember it as Makefile" 9. The same page names the initialization rule: "The default is false, except linkgit:git-clone[1] or linkgit:git-init[1] will probe and set core.ignoreCase true if appropriate when the repository is created" 9. Git figures out the right value at clone time. You should not override it.

For a bulk rename of every capitalised filename in the vault, the mass-rename toolkit covered in Change Something Across Every Note at Once is the right home for the loop. If the rename removes a file you did not mean to remove, the delete-and-recover pattern in Trash and Recover a Deleted Note is the recovery step.

Three caveats the discipline does not fix

The lowercase discipline fixes one shape of trap and leaves three edges intact. Case-collision does not corrupt file content — it creates decidability drift. Flipping core.ignoreCase to false on a case-insensitive file system is unsafe according to three named primary sources. A bulk lowercase rename does not retro-fix files already committed to git history in mixed case.

Caveat one: case-collision does not corrupt file content. The failure mode is ghost duplicates, not lost bytes. A Files.md and a files.md living in the same vault confuse the vault owner, confuse git, and confuse the sync surface — but the eight bytes of the actual file content are still on disk. This is a decidability story, not a data-loss story. Any advice that catastrophises with "your files will be corrupted" is folklore the primary sources themselves refuse to endorse. Fix the ghost duplicates by picking one convention and renaming to it, without panicking about content that was never in danger.

Caveat two: core.ignoreCase=false on APFS or NTFS is unsafe. Microsoft's WSL docs name the exact class of breakage: "Setting this option to false on a case-insensitive file system may lead to confusing errors, false conflicts, or duplicate files" 2. Git's own reference is equally blunt: "Git relies on the proper configuration of this variable for your operating and file system. Modifying this value may result in unexpected behavior" 9. Adam Johnson closes the loop after summarising the same warning himself: "Whilst researching for this post I saw several sources that recommended disabling core.ignoreCase for a repo, or even globally. This will make Git to detect case changes on case-insensitive filesystems, but it's unsafe, as the docs warn... So, yeah, best to leave the option alone" 5. Three named primary sources — the OS vendor, the git project, and a working engineer who verified the docs — say do not do this. The convention is the fix; the config toggle is not.

Caveat three: a bulk lowercase rename does not retro-fix files already in git history. The rename commit lowercases today's files; it does not touch the mixed-case names in yesterday's commits. Anyone who clones an old ref, checks out an old branch, or runs git log --follow on a since-renamed file will still see the old Files.md path, and the old commits will still trip the same collision on a Linux checkout of that ref. A history rewrite — git filter-repo or the older git filter-branch — is the technically correct way to remove the mixed-case paths from every past commit, but it is a disruptive step: it rewrites every commit hash, invalidates every open pull request, and forces every other clone to reset. Most note-vault owners should not take it. Instead, take the honest view: the convention prevents future collisions; existing history stays as it was, and old references are read at the reader's own risk.

Two escape hatches in this space complicate the "just be case-sensitive everywhere" story and are worth naming honestly. Microsoft added per-directory case sensitivity to Windows in build 17107 in May 2018 — Microsoft's WSL docs verbatim: "Support for per-directory case sensitivity began in Windows 10, build 17107. In Windows 10, build 17692, support was updated to include inspecting and modifying the case sensitivity flag for a directory from inside WSL... To change a directory in the Windows file system so that it is case-sensitive (FOO ≠ foo), run PowerShell as Administrator and use the command: fsutil.exe file setCaseSensitiveInfo <path> enable" 2. The Linux kernel added the corresponding opt-in case-insensitive casefold attribute for ext4 in the 5.2 release. Both flags exist. Both require administrator privileges, both toggle at directory-creation time, and both introduce compatibility gaps of their own. A convention that lives in every file's name is a simpler safety floor than a flag that lives on an inode.

The other silent-corruption trap on the same surface

Filename case is one of two byte-level traps a cross-OS note vault hits without a written contract. The other is line endings — the same file rendered as one long line on Windows because a macOS editor wrote LF instead of CRLF. We spelled that one out in the companion to this piece; read the two together.

Together they form the two silent-corruption traps of note-vault-on-git-across-OSes — the pair a note-taker discovers only after the vault has already crossed the second machine. Line Endings in Your Notes is the byte-between-lines companion; this piece is the byte-inside-the-filename companion. Both fix with a discipline committed to the vault root, and both survive editor swaps and OS reinstalls because the discipline lives with the files, not the machine.

A vault that crosses operating systems is easier to reason about if you commit three small files at its root and pick a naming convention. A safe .gitignore — what git should ignore. A safe .gitattributes — what the line-endings companion just described. A lowercase-kebab-ASCII naming convention — what this piece just described. Three files, one folder, and the vault stops surprising you.

There is one more sibling worth naming. Filename case is a form of the "same file, multiple names" family; the other form is user-created aliases. Symlink a Note in Two Places covers that side. Case-collisions are OS-created aliases; symlinks are user-created ones. Different fault line, same instinct: know when two names point to one file, and when they do not.

How this works in MNMNOTE

The vault owner who lives across a Mac, a Windows laptop, and a Linux desktop is exactly who hits this trap. A browser-based, local-first note tool opens the same plain-Markdown vault on Windows, macOS, Linux, iOS, and Android — and the discipline that keeps the vault legible on every one of them belongs to you.

MNMNOTE opens the vault where you cloned it. That is the whole promise. The lowercase-kebab-ASCII convention above is the discipline that pays for the promise: a file named notes.md is a file that resolves to the same eight-byte-lookup on every OS your browser runs on, and every git remote you push to. It is one convention, adopted once at the vault's root, and the ghost-duplicate failure mode never comes back for the notes that already obey it.

Frequently asked questions

Why do my notes show as duplicates on Linux but not on Mac?

Because your vault was authored on a case-insensitive filesystem — APFS on macOS or NTFS on Windows — where Files.md and files.md were the same file. Linux ext4 is case-sensitive by default 4, so pulling the vault onto a Linux server surfaces both names as two files. Rename one of them to the other in git, adopt the lowercase convention, and the duplicates stop.

What is a case-sensitive vs case-insensitive filesystem for a note vault?

A case-sensitive filesystem stores and looks up filenames by their exact bytes — Files.md and files.md are two files. A case-insensitive filesystem stores the case you typed but ignores it during lookup — they are one file. macOS APFS and Windows NTFS are case-insensitive by default; Linux ext4 is case-sensitive. Pick lowercase-kebab-ASCII for the vault to sidestep the difference entirely.

Is APFS case-insensitive by default?

Yes. Apple's own APFS FAQ says so verbatim: "APFS, like HFS+, is case-sensitive on iOS and is available in case-sensitive and case-insensitive variants on macOS, with case-insensitive being the default" 1. The Mac you bought at the Apple Store is case-insensitive out of the box. iPhones and iPads are case-sensitive. That asymmetry is a real gotcha for iCloud-synced text and for iOS Files app users pulling a Mac-authored vault.

Does macOS treat filenames as case-sensitive?

No, not by default. The macOS installer formats the boot volume as APFS in its case-insensitive variant, meaning Notes.md and notes.md are the same file at the file-system layer 1. A user who deliberately picked APFS (Case-sensitive) at install time gets Linux-like behavior and does not hit this trap; everyone who accepted the default does. iOS is the opposite — Apple ships APFS in its case-sensitive variant on iPhone and iPad.

Why does git say nothing changed when I renamed a file to lowercase?

Because core.ignoreCase=true — which git set for you automatically at clone or init time on APFS or NTFS — tells git the rename is a no-op: the OS reports the same directory listing before and after 9. The fix is git mv Notes.md notes.md, which reaches into the git index and records the rename regardless of what the OS reports. For stubborn cases, use the two-step temp-name recipe from Hanselman 6.

How do I rename a folder to change only its case on Mac?

Two-step temp-name rename, verbatim from Scott Hanselman's incident writeup 6: git mv foo foo2 && git mv foo2 FOO && git commit -m "changed case of dir". The single git mv --force will not help because APFS refuses to admit the second directory exists — from its perspective, foo and FOO are the same directory. Renaming through a temporary intermediate is the only way past the case-preserving refusal.

Should I set core.ignoreCase=false to make git see case changes?

No. Microsoft's WSL docs warn verbatim: "Setting this option to false on a case-insensitive file system may lead to confusing errors, false conflicts, or duplicate files" 2. Git's own reference says "Modifying this value may result in unexpected behavior" 9. Adam Johnson, having read both, concludes: "So, yeah, best to leave the option alone" 5. Three named primary sources reject this fix. Rename with git mv and adopt the convention instead.

References


Two silent-corruption traps hit a cross-OS note vault the day it crosses two laptops — one is line endings, and the other is what this piece just spelled out; a discipline you commit once at the vault's root, then stop thinking about, is what mnmnote.com is built to reward.

Footnotes

  1. Apple Inc. Apple File System Guide — Frequently Asked Questions. Apple Developer archive. https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/APFS_Guide/FAQ/FAQ.html — verbatim: "APFS, like HFS+, is case-sensitive on iOS and is available in case-sensitive and case-insensitive variants on macOS, with case-insensitive being the default." Accessed 2026-07-08. 2 3 4

  2. Microsoft Docs (WSL). Case Sensitivity. https://learn.microsoft.com/en-us/windows/wsl/case-sensitivity — verbatim: "Windows file system treats file and directory names as case-insensitive... Linux file system treats file and directory names as case-sensitive"; "Support for per-directory case sensitivity began in Windows 10, build 17107"; "Setting this option to false on a case-insensitive file system may lead to confusing errors, false conflicts, or duplicate files"; command fsutil.exe file setCaseSensitiveInfo <path> enable. Last updated 2025-10-03; accessed 2026-07-08. 2 3 4

  3. Microsoft Docs (Win32 File I/O). Naming Files, Paths, and Namespaces — "Naming Conventions." https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file — verbatim: "Do not assume case sensitivity... Note that NTFS supports POSIX semantics for case sensitivity but this is not the default behavior." Last updated 2025-04-11; accessed 2026-07-08.

  4. kernel.org. ext4 General Information — Case-insensitive file name lookups. https://www.kernel.org/doc/html/latest/admin-guide/ext4.html — verbatim: "in order to enable case-insensitive directories, the filesystem must have the casefold feature, which stores the filesystem-wide encoding model used." Accessed 2026-07-08. 2

  5. Johnson, A. (2022-12-09). Git: How to change the case of filenames. https://adamj.eu/tech/2022/12/09/git-change-case-of-filenames/ — verbatim: "The Linux filesystem ext4 is case sensitive, by default"; "macOS' default APFS is case insensitive, by default"; "So, yeah, best to leave the option alone." Accessed 2026-07-08. 2 3 4 5

  6. Hanselman, S. (2019-06-27). Git is case-sensitive and your filesystem may not be — Weird folder merging on Windows. https://www.hanselman.com/blog/git-is-casesensitive-and-your-filesystem-may-not-be-weird-folder-merging-on-windows — verbatim: "Turns out that six months ago one of us introduced another folder with the name dasblog while the original was DasBlog... When we checked them on Mac or Windows the files ended up in merged into one folder, but on Linux they were/are two, so the build fails"; "If you're renaming a directory, you'll do a two stage rename with a temp name." Accessed 2026-07-08. 2 3 4

  7. rust-lang/cargo GitHub issue #11962, "Projects created on Windows with cargo.toml fail to run on Linux." https://github.com/rust-lang/cargo/issues/11962 — opened 2023-04-12; body opens verbatim "A project created on Windows, where the user decided to use cargo.toml will not run on Linux." Closed 2023-04-26 (state_reason: completed); the trap the report describes is not resolved in Cargo, it was closed as S-needs-design. Accessed 2026-07-08.

  8. Moonbase59 (2021-08-14). Handle filename "casing", "illegal" characters consistently across OS'es. Obsidian Forum thread #22543. https://forum.obsidian.md/t/handle-filename-casing-illegal-characters-consistently-across-os-es/22543 — verbatim: "Files with perfectly legal characters and different 'casing' aren't handled consistently in Obsidian"; "I regard this as a bug. Differently 'cased' filenames are different, at least on Operating Systems like Linux." Thread still active as of 2026-07-08.

  9. git project. Documentation/config/core.adoccore.ignoreCase entry. https://raw.githubusercontent.com/git/git/master/Documentation/config/core.adoc — verbatim: "Internal variable which enables various workarounds to enable Git to work better on filesystems that are not case sensitive, like APFS, HFS+, FAT, NTFS, etc"; "The default is false, except linkgit:git-clone[1] or linkgit:git-init[1] will probe and set core.ignoreCase true if appropriate when the repository is created"; "Git relies on the proper configuration of this variable for your operating and file system. Modifying this value may result in unexpected behavior." Continuously updated; accessed 2026-07-08. 2 3 4 5