Skip to content

Editor attributes the previous tab's content to a restored tab whose rawContent is missing (silent file corruption with auto-save) #213

Description

@PathGao

Describe the bug

When a restored tab has no usable content (rawContent is undefined), switching to it does not swap the Monaco buffer — the editor keeps displaying the previous tab's content under the new tab's title. The next content-change event then writes that stale buffer into the wrong tab's store, and with auto-save enabled the wrong content is written to the wrong file on disk. This is silent data corruption.

How we hit it

We hit this in a version-mix scenario while working on #211: a v2.6.11 build restored a session snapshot written in the newer window-state-only format proposed there (which intentionally carries no rawContent). restoreState assigns the parsed tabs wholesale:

restoreState(jsonBuffer) {
    const data = JSON.parse(jsonBuffer);
    if (data && Array.isArray(data.tabs)) {
        this.tabs = data.tabs;           // ← shape never validated
        ...

so every restored tab had rawContent === undefined. Observed live (screenshot-verified): a window with tabs 3.md (disk content 3333) and 4.md (disk content 4444) displayed 4444 under the 3.md tab after a tab switch. One edit event + auto-save later, 3.md on disk becomes 4444 — we saw exactly that in an earlier uninstrumented run ("both files ended up with the same content").

Why it happens

  1. restoreState trusts the snapshot's shape blindly — no field validation, no version check (the editor never wrote a version field historically).
  2. The editor's tab-switch sync has no guard for non-string content: with content undefined the setValue swap is skipped/throws, leaving the stale buffer visible under the new tab.
  3. The onDidChangeModelContent handler attributes whatever the editor holds to tabManager.activeTabId at event time, so a stale buffer + any change event = misattributed content.

Expected behavior

A tab with missing/invalid restored content should display as empty (or reload from its path), never as another tab's content — and nothing should be auto-saved from a buffer that was never attributed to that tab by the user.

Suggested hardening (all cheap)

  • Normalize in restoreState: default rawContent/originalContent to '', drop entries without the expected fields; ignore snapshots whose version is newer than the build understands.
  • Guard the editor sync: treat non-string tab content as '' before setValue.

Note: #211 side-steps the cross-version trigger by moving the new snapshot format to its own storage key (an older build simply finds nothing to restore after a downgrade), but the editor-side robustness gap is independent of that PR and worth fixing for any malformed snapshot.

Device

  • OS: macOS (Darwin 25.x)
  • App Version: v2.6.11

🤖 Generated with Claude Code

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