Skip to content

Copy-paste between files triggers duplicate/repeat-paste loop #53

@mrnasil

Description

@mrnasil

Bug Description

When YAOS plugin is active, copying content from one Obsidian .md file and pasting into another .md file causes a repeat-paste loop — the pasted content duplicates over and over.

The issue does NOT occur when YAOS is disabled.

Root Cause Analysis

Race condition between editor binding settle window and reconciliation recovery path.

Mechanism

  1. User switches from File A to File B → binding is established for File B with settleWindowMs = 1600ms (fast tab switch)
  2. During settle window, user pastes content into File B
  3. handleLiveEditorUpdate updates lastEditorChangeAtMs but yCollab hasn't fully connected yet → paste does NOT propagate to Y.Text
  4. Obsidian autosave writes to disk → vault.on("modify") → dirty set
  5. Dirty-set drain → handleBoundFileSyncGap → localOnly divergence (editor=disk=pasted, CRDT≠pasted)
  6. Idle guard defers for 3000ms, then recovery applies diff: Y.Text gets pasted content
  7. Binding eventually settles → yCollab syncs editor ↔ Y.Text → detects divergence → inserts paste AGAIN → DUPLICATE

Key Files

  • src/sync/editorBinding.ts:1072lastEditorChangeAtMs set to bind time, not editor activity time
  • src/sync/editorBinding.ts:784-791handleLiveEditorUpdate fires on paste but yCollab may not be connected yet
  • src/runtime/reconciliationController.ts:1583-1643 — localOnly idle guard doesn't check binding health/settle status

Why Existing Guards Don't Catch It

Guard Why It Fails
Idle guard (3000ms) Defers the race, doesn't prevent yCollab's late insert
Amplification quarantine Growth may oscillate, not strictly monotonic
Fingerprint quarantine Each cycle has different (prev,next) → new fingerprint
Recovery lock (1500ms) yCollab's insert is independent, not a recovery operation

Proposed Fix

In the localOnly branch of handleBoundFileSyncGap, check binding health/settle status before running recovery. If binding is still settling or unhealthy, skip recovery and let yCollab handle the paste once settled:

// Before the idle guard check, add:
const bindHealth = editorBindings?.getBindingHealthForView(openViews[0]);
if (bindHealth && (bindHealth.settling || !bindHealth.healthy)) {
    this.amplificationHistory.delete(file.path);
    return true;
}

Environment

  • OS: Windows
  • YAOS version: 1.6.1
  • Reproducible: consistently when YAOS is active

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