Summary
Peer.ts's toLocalPath and toGlobalPath are not inverse functions for paths whose top-level segment begins with _ (e.g. _system/foo.md). The asymmetry produces underscore-less duplicate documents in CouchDB on every round trip, which then replicate back down to every connected LiveSync client as new, parallel files.
Observed on livesync-bridge main at 95e8ce6, Self-hosted LiveSync plugin with E2E encryption enabled, usePathObfuscation: false, CouchDB 3.5.1, Deno 2.3.1.
The asymmetry
Based on Peer.ts:
toLocalPath(global): if global starts with _, prepends / → result begins with /_
toGlobalPath(local): if local starts with _, strips the _ → leading underscore is removed entirely
Round-trip of _system/foo.md:
- LiveSync plugin stores the file in CouchDB with doc ID
/_system/foo.md (plugin's own _→/_ escape for CouchDB's reserved _ namespace).
- Bridge's changes feed reads the CouchDB doc, derives the on-disk path → writes
_system/foo.md to the filesystem ✓
- Bridge's filesystem watcher sees the just-written file and calls
toGlobalPath("_system/foo.md"), which strips the leading _ → system/foo.md
- Bridge PUTs a new CouchDB doc with ID
system/foo.md (no leading /, no leading _)
- LiveSync plugin's changes feed pulls this as a brand-new top-level file
system/foo.md and creates it on the client alongside the original _system/foo.md
The duplicate then fans out to every other connected client.
Reproduction
- Have a vault containing
_system/foo.md (or any top-level directory/file beginning with _).
- Connect the plugin with
usePathObfuscation: false, E2E encryption on.
- Deploy livesync-bridge against the same CouchDB, with a fresh empty
./data/ dir.
- Within the first sync cycle, CouchDB will contain both
/_system/foo.md (original) and system/foo.md (duplicate written back by bridge).
- On the plugin client,
system/foo.md will appear alongside _system/foo.md.
Impact
Any vault that uses a convention of _-prefixed top-level folders or files (commonly used to sort them to the top of Obsidian's file explorer) will produce a silent, self-propagating duplicate for every such document on the first sync with livesync-bridge. The duplicates are byte-identical copies but live at different paths, so they're not deduplicated by LiveSync's chunk store.
Workaround
The only workaround I found is renaming every top-level _-prefixed path in the vault before deploying bridge. In my case: _system/ → 0-system/, _test-fail-check.md → 0-test-fail-check.md. After rename + purging the stale original docs from CouchDB, bridge sync is stable.
Suggested fix
toGlobalPath should handle LiveSync's /_ → _ conversion symmetrically with toLocalPath. Roughly: if the local path starts with _, the global should be / + local (not local.substring(1)). Happy to put up a PR if that matches the intended design — I wanted to file the observation first in case the current behavior is deliberate.
Environment
- livesync-bridge: main @ 95e8ce6
- Deno: 2.3.1 (via denoland/deno:2.3.1 image)
- CouchDB: 3.5.1
- Self-hosted LiveSync plugin: 0.25.27
- Platform: Ubuntu 24.04 in Docker, running as non-root user
Summary
Peer.ts'stoLocalPathandtoGlobalPathare not inverse functions for paths whose top-level segment begins with_(e.g._system/foo.md). The asymmetry produces underscore-less duplicate documents in CouchDB on every round trip, which then replicate back down to every connected LiveSync client as new, parallel files.Observed on livesync-bridge
mainat 95e8ce6, Self-hosted LiveSync plugin with E2E encryption enabled,usePathObfuscation: false, CouchDB 3.5.1, Deno 2.3.1.The asymmetry
Based on
Peer.ts:toLocalPath(global): ifglobalstarts with_, prepends/→ result begins with/_toGlobalPath(local): iflocalstarts with_, strips the_→ leading underscore is removed entirelyRound-trip of
_system/foo.md:/_system/foo.md(plugin's own_→/_escape for CouchDB's reserved_namespace)._system/foo.mdto the filesystem ✓toGlobalPath("_system/foo.md"), which strips the leading_→system/foo.mdsystem/foo.md(no leading/, no leading_)system/foo.mdand creates it on the client alongside the original_system/foo.mdThe duplicate then fans out to every other connected client.
Reproduction
_system/foo.md(or any top-level directory/file beginning with_).usePathObfuscation: false, E2E encryption on../data/dir./_system/foo.md(original) andsystem/foo.md(duplicate written back by bridge).system/foo.mdwill appear alongside_system/foo.md.Impact
Any vault that uses a convention of
_-prefixed top-level folders or files (commonly used to sort them to the top of Obsidian's file explorer) will produce a silent, self-propagating duplicate for every such document on the first sync with livesync-bridge. The duplicates are byte-identical copies but live at different paths, so they're not deduplicated by LiveSync's chunk store.Workaround
The only workaround I found is renaming every top-level
_-prefixed path in the vault before deploying bridge. In my case:_system/→0-system/,_test-fail-check.md→0-test-fail-check.md. After rename + purging the stale original docs from CouchDB, bridge sync is stable.Suggested fix
toGlobalPathshould handle LiveSync's/_→_conversion symmetrically withtoLocalPath. Roughly: if the local path starts with_, the global should be/+ local (notlocal.substring(1)). Happy to put up a PR if that matches the intended design — I wanted to file the observation first in case the current behavior is deliberate.Environment