Skip to content

Top-level underscore-prefixed paths: toGlobalPath strips leading _ where toLocalPath prepends /, causing duplicate docs on every sync #43

Description

@citizen-why

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:

  1. LiveSync plugin stores the file in CouchDB with doc ID /_system/foo.md (plugin's own _/_ escape for CouchDB's reserved _ namespace).
  2. Bridge's changes feed reads the CouchDB doc, derives the on-disk path → writes _system/foo.md to the filesystem ✓
  3. Bridge's filesystem watcher sees the just-written file and calls toGlobalPath("_system/foo.md"), which strips the leading _system/foo.md
  4. Bridge PUTs a new CouchDB doc with ID system/foo.md (no leading /, no leading _)
  5. 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

  1. Have a vault containing _system/foo.md (or any top-level directory/file beginning with _).
  2. Connect the plugin with usePathObfuscation: false, E2E encryption on.
  3. Deploy livesync-bridge against the same CouchDB, with a fresh empty ./data/ dir.
  4. Within the first sync cycle, CouchDB will contain both /_system/foo.md (original) and system/foo.md (duplicate written back by bridge).
  5. 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.md0-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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions