Project picker shows duplicate Recent rows for one folder due to path-casing (case-insensitive APFS): /Dev vs /dev
Summary
The desktop app's project picker ("Recent" list) shows duplicate rows for the same project — one under /Users/<user>/Dev (real, uppercase D) and a ghost under /Users/<user>/dev (lowercase). On a case-insensitive APFS volume these are the same inode (one folder on disk), so the lowercase row points at a path that does not independently exist.
Environment
- macOS, case-insensitive APFS (default)
- Claude desktop app (Electron), project picker / FleetView
Steps to reproduce
- On a case-insensitive volume, at some point open/launch a project using a lowercase path (e.g. typed in chat, a shell
cd, or a tool result that contains/Users/<user>/dev/foo). - Open the project picker.
- The project appears twice: once under
…/Dev/fooand once under…/dev/foo.
What I expected
The picker should normalize path casing (or dedupe by resolved inode) so one on-disk folder yields exactly one row.
What actually happens
Two rows per project. Notably:
- The duplication is per-project and tied to projects that were opened before the casing issue was understood; a newer project only ever opened via the uppercase path shows a single row.
- The ghost rows are not sourced from
~/.claude.json(cleaning theprojectsdict there does not remove them) nor from theclaude-code-sessionsstore (allcwd/originCwdvalues are uppercase). They appear to be persisted in the app's IndexedDB recents store (~/Library/Application Support/Claude/IndexedDB/https_claude.ai_0.indexeddb.*), which is why config-level cleanups never fix it.
Why it's stubborn / data-risk note
On case-insensitive APFS, /Dev and /dev resolve to the same inode. Users (or agents) attempting to "clean up" the lowercase path on disk risk rm -rf-ing the real project folder. Please fix by normalizing/deduping casing in the recents store, not by anything that touches the filesystem. A built-in "these two recents resolve to the same inode, collapse them" pass would prevent both the cosmetic dupes and the footgun.
Suggested fix
- Canonicalize project paths (resolve real-path + case) before writing to the recents object store, and dedupe existing entries by resolved inode on load.
4 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Update — the ghost rows are not in local storage; they appear to be server-synced.
Further diagnosis on a case-insensitive APFS machine:
~/.claude.jsonhas zero lowercase/devreferences (every nested value checked).~/Library/Application Support/Claudetree contained the lowercase path string in only one IndexedDB blob.~/.claude/projectscontains only uppercase-Devencoded dirs (and not even all the affected projects), so it isn't the source either.Net: there is no lowercase path persisted locally that the picker could be rendering. The duplicate
…/dev/…recents survive a local store wipe, which strongly implies the Recent projects list is synced from account/server state, where pre-existing entries were recorded with non-canonical path casing and are never deduped by resolved inode.Repro signal: projects opened before the casing was canonicalized show both
/Devand/devrows; a project created/opened only after shows a single/Devrow.Ask: normalize/canonicalize workspace path casing (and dedupe by resolved real-path) server-side on the recents list, and ideally expose a per-row "remove from recents" so users can clear stale entries. Please do not require any client-side filesystem operation — on case-insensitive volumes
/Devand/devare the same inode and a disk "cleanup" risks deleting the real project.Severity note — this is not cosmetic; it can cause irreversible data loss.
The duplicate rows look harmless, but they train users toward a destructive action. On a case-insensitive volume (default APFS / macOS, also Windows),
/Users/<user>/Devand the lowercase-dvariant are the same inode — one folder. A user who sees an unfamiliar lowercase row next to their real uppercase one will reasonably try to "clean up the duplicate" by deleting the lowercase path — and anrm -rfon it resolves to and destroys the real project directory. This has already happened (anrm -rfon the case-variant path wiped a user's projects).So the bug doesn't just show a cosmetic dupe — it manufactures the exact conditions for an irreversible mistake.
The fix is cheap and already identified in the linked threads: canonicalize the working directory with
fs.realpathSync()(resolve real-path + case) before keying project/recents entries, and dedupe existing entries by resolved inode on load. This both removes the duplicate rows and eliminates the data-loss footgun. Please do not ship any remediation that asks users to delete a path on disk.Requesting this be reconsidered rather than auto-closed as a duplicate of the NOT_PLANNED issues — those were triaged as cosmetic; the data-loss angle changes the severity.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.