microsoft / microsoft/Huabu

Parallel Agent writes frequently trigger 'Space modified elsewhere' conflicts

Open
#98 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug partially resolved
Dominant language
TypeScript
Stars
144
Forks
18
Avg merge
2d 6h
Merged PRs (30d)
43

Description

Status — Partially resolved

Phase 1 merged in #103 on 2026-08-15. It fixes the common false global Reload state caused by stale structure-save acknowledgements racing Canvas Sync: versions now reconcile monotonically, covered 409 responses retry automatically, and a later SSE update can clear an earlier conflict and reschedule the save.

The issue remains open. Follow-up work still includes unified snapshot/update-gap reconciliation, dirty-safe reconnect behavior, delta-log backfill, browser structure writes joining the server write coordinator, isolation of derived writes, and node/field-scoped genuine conflict handling.


Problem

With multiple Agent Nodes running in parallel against the same Space, the browser frequently shows:

This Space was modified elsewhere. Your recent edits won't be saved.

The warning offers a Reload action, and it can recur often enough that concurrent Agent work becomes disruptive. The Space otherwise appears to update smoothly until the conflict is raised.

Observed conditions

  • Multiple Agent Nodes / external Agents write to the same Space concurrently.
  • The frequency appears higher after recent real-time sync and Preview Workspace changes.
  • Relevant recent merges to evaluate:
    • #94 — unified Headless/RFS Agent turn streaming, Canvas Sync handshake/heartbeat/reconnect, snapshot convergence
    • #82 / #91 — unified tabbed Preview Workspace and subsequent workspace/canvas interaction changes

No causal attribution to these PRs is confirmed yet. PR #94 is the primary regression boundary because it directly changed Canvas Sync; #82/#91 should be ruled in or out through history/bisect rather than assumed responsible.

Current behavior in code

  • Structure saves use the local Canvas version. A stale save receives CANVAS_VERSION_CONFLICT (HTTP 409), sets a sticky versionConflict, blocks subsequent autosaves, and asks the user to reload.
  • canvasSyncStore applies an update only when fromVersion === local version.
  • If the client detects a version gap and has no pending content edits, it calls loadCanvas() to converge.
  • If pending content edits exist, gap healing is intentionally skipped to avoid clobbering local work; the next autosave is allowed to reach the existing 409 arbitration path.
  • There is not yet delta-log backfill for incremental gap healing, per docs/architecture/canvas-realtime-sync.md.

With several Agents rapidly advancing the server version, a missed, delayed, reordered, or reconnect-boundary update can therefore leave the tab stale. Local autosave then turns that stale baseline into the persistent warning/reload flow.

Investigation requested

Instrument and reproduce parallel Agent writes while recording:

  • client Canvas version before/after every snapshot and update
  • each event's fromVersion / toVersion
  • reconnect and snapshot boundaries
  • pending content-node IDs and pending structure saves
  • every loadCanvas() invocation and concurrent load
  • structure PUT expected/server versions on 409
  • originating Agent/thread and server write order

Determine whether conflicts come from:

  1. genuinely concurrent user edits that require arbitration
  2. dropped/reordered SSE updates or reconnect gaps
  3. overlapping snapshot-driven and primary Canvas loads
  4. local structure autosaves racing Agent broadcasts
  5. derived writes such as height/preprocessing saves
  6. a regression introduced by #94

Expected

  • Parallel Agent writes that target unrelated nodes should converge without forcing a full browser reload.
  • Recoverable version gaps should heal automatically.
  • Genuine same-field conflicts should preserve local work and provide targeted resolution rather than globally disabling all Canvas autosaves.
  • The warning should be rare and explain which write conflicted.

Acceptance criteria

  • A deterministic multi-Agent reproduction test exists.
  • Unrelated Agent writes no longer cause a global sticky version conflict.
  • Reconnect/snapshot boundaries converge without a spurious structure PUT or 409.
  • Genuine conflicts remain lossless and actionable.
  • Regression coverage includes several concurrent Agent/Headless writers and a browser tab with pending local content.

Related

  • #94 — recently merged real-time sync work and primary regression boundary
  • #97 — slow browser refresh; separate root cause, but this issue amplifies it by repeatedly requiring Reload

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read docs/architecture/canvas-realtime-sync.md and trace canvasSyncStore, loadCanvas(), and the structure PUT conflict path. Instrument the listed version, reconnect, pending-edit, and write-order events, then reproduce concurrent Agent writes. Done means a deterministic test covers parallel writers, reconnect and snapshot boundaries, pending local content, and targeted handling of genuine conflicts without a global sticky conflict.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
distributed-systems, frontend, testing-qa
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.