Skip to content

Fix: large text edits crashed live collab (stack overflow in the text RGA) - #47

Merged
nyblnet merged 1 commit into
mainfrom
fix-crdt-large-text-overflow
Jul 25, 2026
Merged

Fix: large text edits crashed live collab (stack overflow in the text RGA)#47
nyblnet merged 1 commit into
mainfrom
fix-crdt-large-text-overflow

Conversation

@nyblnet

@nyblnet nyblnet commented Jul 25, 2026

Copy link
Copy Markdown
Owner

A text element of roughly 200 KB or more broke live collaboration entirely
— and not just for that element. Surfaced by a background agent while it was
doing unrelated work; I reproduced and bisected it against the real engine
before fixing.

Cause

tokenize() is per-character, so ~200 KB of text is ~200,000 tokens.
applyTxtToState inserted them with:

st.toks.splice(idx, 0, ...toks)

Spreading 200k+ values as call arguments overflows V8's stack. Measured
against the real engine: 100 KB fine, 200 KB and above throw RangeError.

Why it was so damaging

The throw happens inside diff(), which session.flush() calls on every
debounced edit. So once a document contained one large text element,
flush() threw and nothing synced for the rest of the session — every
later edit, on any slide, silently lost to collaborators.

And it's invisible: no error surfaces, peers simply stop receiving. A user
would experience it as "collaboration randomly stopped working."

Fix

Splice without a spread — slice/concat instead. One extra array copy on an
operation that already allocates the token array.

Verified

  • Repro passes at 200 / 300 / 500 KB.
  • Convergence rig SEEDS=300 ALL PASS (46,402 checks) — the mandatory gate
    for any crdt.ts change.
  • Plus a targeted large-text convergence test the rig doesn't cover (it uses
    short strings): two replicas concurrently editing a 300 KB element converge
    to identical html with both contributions preserved.
  • tsc -b clean.

Scope

One line of behaviour change in crdt.ts, deliberately kept minimal given
it's the highest-risk file in the repo. Independent of the relay work in #43
and the client work in #45 — this would still break collab with both of those
merged.

Worth noting the rig didn't catch this because it only generates short
strings. A follow-up worth considering: add a large-text case to
scripts/test-sync.ts so the gate covers this class.

… RGA)

A text element of roughly 200KB or more broke live collaboration entirely —
not just for that element. Found while verifying a background agent's report;
reproduced and bisected against the real engine.

CAUSE. tokenize() is per-character, so ~200KB of text is ~200,000 tokens.
applyTxtToState inserted them with

    st.toks.splice(idx, 0, ...toks)

and spreading 200k+ values as call ARGUMENTS overflows V8's stack. Measured:
100KB fine, 200KB and up throw RangeError.

WHY IT WAS SO BAD. The throw happens inside diff(), which session.flush()
calls on every debounced edit — so once a document contained a large text
element, flush() threw and NOTHING synced for the rest of the session. Every
subsequent edit, on any slide, was silently lost to collaborators. The failure
is invisible: no error surfaces, peers just stop receiving.

FIX. Splice without a spread — slice/concat instead. Costs one array copy on
an operation that already allocates the token array.

VERIFIED. Repro now passes at 200/300/500KB. Convergence rig SEEDS=300 ALL
PASS (46,402 checks). Plus a targeted large-text convergence test the rig
doesn't cover (it uses short strings): two replicas concurrently editing a
300KB element converge to identical html with BOTH contributions preserved.

Independent of the relay work — this would still break collab with the relay
limits fixed.
@nyblnet
nyblnet merged commit 14702e5 into main Jul 25, 2026
1 check passed
@nyblnet
nyblnet deleted the fix-crdt-large-text-overflow branch July 25, 2026 08:09
nyblnet added a commit that referenced this pull request Jul 25, 2026
Current: the maintainer reviews every PR before it reaches main, agents
included. No auto-merge. Visibility into what the agents produce matters more
than throughput while the multi-agent workflow is still being shaken out.

Records the supporting config already in place (one required approval, CI as a
required status check so a red build cannot merge, admin bypass retained).

Also records the FUTURE ACTION discussed but deliberately not taken: when
review becomes the bottleneck, consider auto-merging app-zone PRs on green CI
— with a permanent human-review exclusion list for the paths where a bad merge
is silent or catastrophic (kernel/src/, slides/src/sync/ especially crdt.ts,
server/, the release and signing scripts, and anything touching the splice
contract or update-manifest shape). Notes that the exclusion list must be
enforceable rather than merely documented before auto-merge is enabled, and
that the convergence rig is necessary but not sufficient for crdt.ts — it only
generates short strings, which is how the large-text overflow in #47 slipped
through.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant