Skip to content

Fix LSP latency, mutex contention, and session correctness#3961

Draft
rossirpaulo wants to merge 1 commit into
canaryfrom
paulo/lsp-latency-and-mutex-fix
Draft

Fix LSP latency, mutex contention, and session correctness#3961
rossirpaulo wants to merge 1 commit into
canaryfrom
paulo/lsp-latency-and-mutex-fix

Conversation

@rossirpaulo

@rossirpaulo rossirpaulo commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Make source revisions and runtime-input revisions authoritative for engine installation, diagnostics, test discovery, and run/test admission.
  • Replace the split LSP dispatch paths with one bounded stdio/browser ingress scheduler with typed errors, lifecycle barriers, cancellation ownership, bounded output, and durable/coalesced diagnostics and catalog delivery.
  • Isolate connection-owned capabilities, document overlays, diagnostic URI/version history, and outbound routing while keeping project/runtime state process-owned.
  • Add demand-gated runtime preparation, stale-tree and run-stream fencing, reconnect/session reset handling, and exact ownership routing in the VS Code and playground clients.
  • Memoize inlay annotations and semantic tokens, and apply negotiated UTF-8/UTF-16 position conversion across the LSP boundary while keeping playground positions fixed at UTF-16.
  • Carry the same runtime-input invalidation and session semantics through the native and WASM playground implementations.

Root cause

The debounced engine rebuild introduced a second thread that could hold the project database mutex while interactive requests were running. Request paths treated ordinary contention as an error, older build candidates could install after newer edits, and busy diagnostic reads could look like an empty result. Stdio, browser LSP, and playground delivery also used separate or lossy routing paths, which allowed stale session output and unbounded queues.

User impact

Interactive requests no longer fail with the database-mutex -32001 regression, final diagnostics are retained and retried, browser takeover cannot leak old-session output, and Run/Test actions only use the current source/runtime inputs. The playground renders immediately while the selected project prepares and keeps prior test discovery visible but disabled when stale.

Performance comparison

Same-machine measurements used /tmp/baml-inlay-repro with 400 generated functions, comparing the installed 0.14.1 toolchain with this PR's optimized baml-cli. The final driver waits for the ordered initialize response before sending initialized, as required by LSP.

Metric 0.14.1 This PR Change
Typing inlay p50 313.6 ms 3.4 ms 92x faster
Typing inlay p95 379.4 ms 57.7 ms 6.6x faster
10 ms request-spam p50 2,411.1 ms 2.6 ms 927x faster
10 ms request-spam p95 4,662.9 ms 3.2 ms 1,457x faster
Request-spam maximum 4,946.7 ms 62.3 ms 79x faster
First idle inlay 66.8 ms 8.2 ms 8.1x faster
Fifth queued idle inlay 317.0 ms 18.3 ms 17.3x faster
Post-pause -32001 failures 46-57 per feature/window 0 eliminated
Unanswered requests 144-160 0 eliminated
Diagnostic publications while typing 95 1 trailing update 99% fewer
Catalog notifications 95 1 99% fewer
Explicit cancellation rejected/ignored -32800 correct protocol behavior

At 1,500 functions, the deliberately worst-case cold-typing workload remains expensive because every edit rewrites every generated function: p50 was about 1.15 s on 0.14.1 and 1.38 s on this PR. The reliability regression is still eliminated: approximately 115 mutex errors and 198 unanswered requests became zero errors and zero unanswered requests. Once source is unchanged, the 1,500-function blast completed 240/240 inlay, hover, and completion requests successfully.

Diagnostic delivery now intentionally uses a 150 ms trailing debounce. At 400 functions, final-edit diagnostics moved from about 59.5 ms to 185 ms while avoiding 94 redundant diagnostic and catalog rebuilds during continuous typing.

Validation

  • Rust: 246 focused unit tests across LSP actions, project/runtime, LSP server, and WASM; memoization tests also pass.
  • cargo clippy -p bex_project -p baml_lsp_server -p bridge_wasm -p baml_lsp2_actions --all-targets -- -D warnings
  • cargo check -p bridge_wasm --target wasm32-unknown-unknown
  • Fresh release WASM build and optimized baml-cli build.
  • TypeScript typechecks for playground, editor, VS Code extension/webview, and Prompt Fiddle.
  • Frontend tests: playground 134/134, VS Code webview 25/25, extension 15/15.
  • External latency harness at 400 and 1,500 functions: zero request errors and zero unanswered requests in all final modes; 400-function typing inlay p50 was 3.4 ms; explicit cancellation returned -32800.
  • Repository pre-commit hooks, formatting, and diff checks pass.

Summary by CodeRabbit

  • New Features

    • Added project runtime lifecycle controls, including prepare, retry, release, and status reporting.
    • Added session-aware Playground updates that prevent stale results from appearing after reconnects or project changes.
    • Added UTF-16 source-position negotiation for more accurate editor navigation and semantic highlighting.
    • Improved VS Code project detection and routing for nested projects, symlinks, and multiple workspaces.
  • Bug Fixes

    • Prevented outdated execution snapshots and patches from overwriting newer results.
    • Improved LSP cancellation, backpressure, reconnect, and document synchronization behavior.
    • Added safe rollback for failed testset expansion.

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jul 10, 2026 12:47am
promptfiddle Ready Ready Preview, Comment Jul 10, 2026 12:47am
promptfiddle2 Ready Ready Preview, Comment Jul 10, 2026 12:47am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The change introduces a shared LSP runtime with bounded transport handling, negotiated source-position encoding, project runtime leasing and fencing, updated playground protocols, canonical VS Code project routing, Salsa query memoization, and testset/profiling lifecycle updates.

Changes

LSP runtime and transport

Layer / File(s) Summary
Ingress scheduling and session dispatch
baml_language/crates/bex_project/src/lsp_ingress.rs, baml_language/crates/baml_lsp_server/src/lsp_runtime.rs
Adds bounded session admission, cancellation ownership, response ordering, document overlay tracking, browser takeover handling, and revocable transport sinks.
Framed transport integration
baml_language/crates/baml_lsp_server/src/lib.rs, native_lsp_sender.rs, playground_server.rs
Replaces direct message queues with budgeted outbound frames, custom stdio JSON-RPC framing, and runtime-backed stdio/WebSocket sessions.

Position encoding and project LSP

Layer / File(s) Summary
Position codec and diagnostics
baml_language/crates/baml_project/position.rs, db.rs, baml_language/crates/bex_project/src/bex_lsp/multi_project/diagnostics.rs
Adds negotiated UTF-8/UTF-16 conversion, line-ending validation, semantic-token segmentation, and diagnostic snapshot states.
Request and API migration
baml_language/crates/bex_project/src/bex_lsp/mod.rs, multi_project/request.rs, multi_project/notification.rs
Threads position codecs through LSP capabilities, requests, diagnostics, document overlays, cancellation, and playground commands.

Playground runtime lifecycle

Layer / File(s) Summary
Runtime contracts and execution
baml_language/crates/bex_project/src/bex_lsp/mod.rs, baml_language/crates/bridge_wasm/*, baml_language/crates/baml_lsp_server/playground_server.rs
Adds runtime status/catalog payloads, runtime leasing commands, asynchronous run preparation, explicit completion, and typed cancellation.
Session fencing and UI readiness
typescript2/pkg-playground/src/*, typescript2/app-promptfiddle/src/playground/*
Fences project payloads by session, incarnation, revision, and generation; resets stale run state; and gates execution, previews, test collection, and graph requests on runtime readiness.

VS Code integration

Layer / File(s) Summary
Ownership routing
typescript2/app-vscode-ext/src/projectRoots.ts, extension.ts, src/__tests__/projectRoots.test.ts
Adds canonical filesystem identities, semantic and ownership root resolution, routable patterns, per-owner clients, and coordinator-managed lifecycle.
Source navigation
typescript2/pkg-playground/src/protocol.ts, typescript2/pkg-editor/src/MonacoEditor.tsx, typescript2/app-vscode-ext/src/panels/*, typescript2/app-vscode-webview/src/App.tsx
Propagates the fixed UTF-16 source-position contract with legacy fallback behavior.

Supporting behavior

Layer / File(s) Summary
Expansion, memoization, and lifecycle tests
baml_language/crates/baml_builtins2/.../registry.baml, baml_language/crates/baml_lsp2_actions/*, baml_language/crates/bex_engine/*
Adds rollback-aware testset expansion, Salsa memoization tests, explicit profiling activation, host-call identity retention, and cursor monotonicity checks.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: imalsogreg

Poem

I’m a rabbit with packets to route,
Through framed little tunnels they hop in and out.
UTF-16 carrots line every track,
Stale builds are fenced from sneaking back.
Memoized lettuce grows fresh and bright—
Runtime leases keep the burrow right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main themes: LSP latency, mutex contention, and session correctness.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch paulo/lsp-latency-and-mutex-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@github-actions

Copy link
Copy Markdown

Binary size checks failed

4 violations · ✅ 3 passed

⚠️ Please fix the size gate issues or acknowledge them by updating baselines.

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 22.3 MB 9.6 MB file 21.5 MB +779.8 KB (+3.6%) FAIL
packed-program Linux 🔒 16.0 MB 6.7 MB file 15.6 MB +358.3 KB (+2.3%) OK
baml-cli macOS 🔒 17.2 MB 8.3 MB file 16.6 MB +596.9 KB (+3.6%) FAIL
packed-program macOS 🔒 12.4 MB 5.9 MB file 12.1 MB +281.8 KB (+2.3%) OK
baml-cli Windows 🔒 18.8 MB 8.5 MB file 18.1 MB +645.6 KB (+3.6%) FAIL
packed-program Windows 🔒 13.3 MB 6.0 MB file 13.0 MB +292.8 KB (+2.3%) OK
bridge_wasm WASM 14.9 MB 🔒 4.2 MB gzip 4.1 MB +176.1 KB (+4.3%) FAIL

🔒 = the size this artifact is GATED on (ceiling + delta). Binaries gate on file size (installed binary); WASM gates on gzip (download size). The other size is shown for information only.

Details & how to fix

Violations:

  • baml-cli (Linux) file_bytes: 22.3 MB exceeds limit of 22.2 MB (exceeded by +133.9 KB, policy: max_file_bytes)
  • baml-cli (Linux) file_delta_pct: +3.6% exceeds limit of 3.0% (exceeded by +0.6pp, policy: max_delta_pct)
  • baml-cli (macOS) file_bytes: 17.2 MB exceeds limit of 17.1 MB (exceeded by +100.3 KB, policy: max_file_bytes)
  • baml-cli (macOS) file_delta_pct: +3.6% exceeds limit of 3.0% (exceeded by +0.6pp, policy: max_delta_pct)
  • baml-cli (Windows) file_bytes: 18.8 MB exceeds limit of 18.7 MB (exceeded by +102.2 KB, policy: max_file_bytes)
  • baml-cli (Windows) file_delta_pct: +3.6% exceeds limit of 3.0% (exceeded by +0.6pp, policy: max_delta_pct)
  • bridge_wasm (WASM) gzip_bytes: 4.2 MB exceeds limit of 4.2 MB (exceeded by +54.3 KB, policy: max_gzip_bytes)
  • bridge_wasm (WASM) gzip_delta_pct: +4.3% exceeds limit of 3.0% (exceeded by +1.3pp, policy: max_delta_pct)

Add/update baselines:

.ci/size-gate/aarch64-apple-darwin.toml:

[artifacts.baml-cli]
file_bytes = 17152688
stripped_bytes = 17152736
gzip_bytes = 8294648

.ci/size-gate/wasm32-unknown-unknown.toml:

[artifacts.bridge_wasm]
file_bytes = 14917601
gzip_bytes = 4235047

.ci/size-gate/x86_64-pc-windows-msvc.toml:

[artifacts.baml-cli]
file_bytes = 18760192
stripped_bytes = 18760192
gzip_bytes = 8510483

.ci/size-gate/x86_64-unknown-linux-gnu.toml:

[artifacts.baml-cli]
file_bytes = 22310784
stripped_bytes = 22310776
gzip_bytes = 9550926

Generated by cargo size-gate · workflow run

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
baml_language/crates/baml_lsp_server/src/playground_server.rs (1)

1462-1494: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Update the document mirror only after successful delivery.

The mirror is advanced before budget reservation and broadcast. If either fails, the browser misses the edit, while later identical file events are suppressed as echoes.

Proposed fix
 {
-    let Ok(mut mirror) = doc_mirror.lock() else {
+    let Ok(mirror) = doc_mirror.lock() else {
         continue;
     };
     if mirror.get(&canonical).map(String::as_str) == Some(content.as_str()) {
         continue;
     }
-    mirror.insert(canonical.clone(), content.clone());
 }
 ...
-let _ = lsp_out_tx.send(frame);
+if lsp_out_tx.send(frame).is_err() {
+    continue;
+}
+if let Ok(mut mirror) = doc_mirror.lock() {
+    mirror.insert(canonical.clone(), content.clone());
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_lsp_server/src/playground_server.rs` around lines
1462 - 1494, Update the document mirror only after the notification frame has
been successfully reserved and sent. In the disk-change handling block, move the
existing `mirror.insert` in coordination with `lsp_out_budget.try_message` and
`lsp_out_tx.send`, ensuring all saturation, oversized, serialization, and send
failures leave the previous mirror value unchanged so a later event can be
retried.
typescript2/pkg-playground/src/ExecutionPanel.tsx (1)

2517-2562: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Avoid issuing the initial collection request twice.

The first effect posts after setting pendingTestTarget; the next effect observes that target with no tree and posts again. Let the centralized effect own collection requests.

Proposed fix
     setPendingTestTarget({
       project: selectedProject,
       kind: initialTestName ? 'test' : 'testset',
       name: initialTestName ?? initialTestsetName!,
     });
-    port.postMessage({ type: 'requestCollectTests', project: selectedProject });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript2/pkg-playground/src/ExecutionPanel.tsx` around lines 2517 - 2562,
Remove the direct port.postMessage requestCollectTests call from the
initial-target useEffect, leaving it responsible only for initializing state and
pendingTestTarget. Let the pendingTestTarget collection useEffect handle the
single centralized request after the target is set, while preserving its
existing readiness and project guards.
🧹 Nitpick comments (1)
baml_language/crates/bex_engine/tests/collect_tests.rs (1)

172-206: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding this as a unit test in src/lib.rs instead of an integration test.

The new test helpers and test_registry_expansion_can_be_rolled_back_before_publication test exercise the public engine.call_function API, which is equally accessible from #[cfg(test)] unit tests. The existing integration test structure is consistent, but the coding guideline prefers unit tests where possible for faster feedback and access to internal state.

As per coding guidelines: "Prefer writing Rust unit tests over integration tests where possible" and "Always run cargo test --lib if you changed any Rust code". Ensure cargo test --lib passes for the changed Rust crates (bex_events, baml_lsp_server, bex_engine).

Also applies to: 751-786

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bex_engine/tests/collect_tests.rs` around lines 172 -
206, Move the rollback expansion test, including the helpers
registry_is_expanded and rollback_testset_expansion and
test_registry_expansion_can_be_rolled_back_before_publication, from the
integration test module into the appropriate #[cfg(test)] unit-test section of
src/lib.rs. Preserve coverage through the public engine.call_function API,
adjust imports and setup as needed, and run cargo test --lib for bex_events,
baml_lsp_server, and bex_engine.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@baml_language/crates/baml_lsp_server/src/lib.rs`:
- Around line 51-61: Update OutboundFrame payload storage so clones share the
underlying Message/Value via Arc instead of deep-cloning payload data while
sharing one OutboundCharge; adjust constructors and all payload accesses,
including the writer match, to use payload.as_ref(), and apply the same change
to the additional affected clone/delivery sites.
- Around line 227-267: The LSP header parsing loop can allocate unbounded memory
before validating Content-Length. In the header-reading logic, replace unbounded
read_line usage with a capped reader or bounded incremental reads, and track
cumulative header bytes against a fixed maximum header-size limit; return
StdioReadError::Framing when the limit is exceeded, including for unterminated
headers, while preserving CRLF and malformed-header validation.

In `@baml_language/crates/baml_lsp_server/src/lsp_runtime.rs`:
- Line 492: The lsp_runtime response branch currently discards all client
responses, leaving requests created by make_request unresolved. Update BexLsp’s
message handling and scheduler dispatch to correlate each
lsp_server::Message::Response with its pending request and deliver the result,
or remove the unsupported server-request/make_request functionality
consistently.

In `@baml_language/crates/bex_project/src/bex_lsp/mod.rs`:
- Around line 302-314: Make prepared-run registration RAII-backed by turning
PreparedFunctionRun and PreparedTestRun into non-Copy lease handles that own the
registered call_id. Implement Drop for the handle to invoke the equivalent of
finish_project_run and release runtime/cancellation state, ensuring cleanup on
normal completion, panic, or task abort. Update preparation and detached-task
call sites to retain and drop the lease instead of manually calling
finish_project_run.

In `@baml_language/crates/bex_project/src/bex_lsp/multi_project/notification.rs`:
- Around line 45-61: Preserve per-session ownership of shared document overlays
in the open-document handling and corresponding change/close paths (including
the regions around the referenced lines). Update the BexProject overlay identity
or ownership tracking so one LSP connection cannot overwrite another session’s
unsaved text or restore disk contents while another session remains open; only
remove/restore an overlay after its final owner closes. Add a test covering two
sessions opening, changing, and closing the same document.

In `@baml_language/crates/bex_project/src/lsp_ingress.rs`:
- Around line 717-721: Update the policy construction in the message-handling
logic to preserve the forced cancellation setting for protected methods. Combine
the forced and requested cancellation policies using the same precedence rule as
class strength, ensuring custom policies cannot downgrade `initialize`,
`shutdown`, or `workspace/executeCommand` from `NonCancellableWhenRunning`; use
the existing `MessagePolicy::for_message` and `stronger_class` logic as
references.

In `@typescript2/app-promptfiddle/src/playground/worker-backend.ts`:
- Around line 187-190: Update the worker initialization flow around
workerReadyPromise to reject when the Worker emits an error and to reject after
a finite timeout if no ready message arrives. Ensure connect propagates either
failure instead of hanging, and clean up the error listener and timeout when
workerReadyPromise resolves or rejects.

In `@typescript2/app-vscode-ext/src/extension.ts`:
- Around line 596-609: Preserve the startup error recorded by ensureClient when
every client fails to start. Update the surrounding startup flow and
updateAggregateServerState so an empty clients map does not overwrite an
existing error state with stopped; only transition to stopped when no startup
error is present.

In `@typescript2/pkg-playground/src/ports/WebSocketRuntimePort.ts`:
- Around line 579-587: Update the server-side
WsOutMessage::ControlFlowGraphResult variant and every producer to serialize
sessionEpoch, project, projectIncarnation, sourceRevision, generation,
derivedEpoch, functionName, and graph; ensure the TypeScript
controlFlowGraphResult handler receives these fields so its sessionEpoch fence
and identity metadata are valid.
- Around line 150-198: Preserve the latest desired runtime lease across
pre-handshake sends and reconnects instead of deleting it in connect() or hello
processing. Update the hello-success path to replay desiredRuntimeLease exactly
once after confirming playground compatibility and runtimeDemandSupported, then
clear or mark it as sent only after transmission. Ensure ensureProjectRuntime
and releaseProjectRuntime continue updating the lease while disconnected,
without entering the generic reconnect queue.

---

Outside diff comments:
In `@baml_language/crates/baml_lsp_server/src/playground_server.rs`:
- Around line 1462-1494: Update the document mirror only after the notification
frame has been successfully reserved and sent. In the disk-change handling
block, move the existing `mirror.insert` in coordination with
`lsp_out_budget.try_message` and `lsp_out_tx.send`, ensuring all saturation,
oversized, serialization, and send failures leave the previous mirror value
unchanged so a later event can be retried.

In `@typescript2/pkg-playground/src/ExecutionPanel.tsx`:
- Around line 2517-2562: Remove the direct port.postMessage requestCollectTests
call from the initial-target useEffect, leaving it responsible only for
initializing state and pendingTestTarget. Let the pendingTestTarget collection
useEffect handle the single centralized request after the target is set, while
preserving its existing readiness and project guards.

---

Nitpick comments:
In `@baml_language/crates/bex_engine/tests/collect_tests.rs`:
- Around line 172-206: Move the rollback expansion test, including the helpers
registry_is_expanded and rollback_testset_expansion and
test_registry_expansion_can_be_rolled_back_before_publication, from the
integration test module into the appropriate #[cfg(test)] unit-test section of
src/lib.rs. Preserve coverage through the public engine.call_function API,
adjust imports and setup as needed, and run cargo test --lib for bex_events,
baml_lsp_server, and bex_engine.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8340b0e8-a911-4916-a12d-91db384d37fe

📥 Commits

Reviewing files that changed from the base of the PR and between 7cf47cf and 17c6e76.

⛔ Files ignored due to path filters (2)
  • baml_language/Cargo.lock is excluded by !**/*.lock
  • typescript2/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (70)
  • baml_language/crates/baml_builtins2/baml_std/testing/registry.baml
  • baml_language/crates/baml_cli/src/paint.rs
  • baml_language/crates/baml_lsp2_actions/src/annotations.rs
  • baml_language/crates/baml_lsp2_actions/src/lib.rs
  • baml_language/crates/baml_lsp2_actions/src/tokens.rs
  • baml_language/crates/baml_lsp2_actions_tests/Cargo.toml
  • baml_language/crates/baml_lsp2_actions_tests/src/lib.rs
  • baml_language/crates/baml_lsp2_actions_tests/src/memoization.rs
  • baml_language/crates/baml_lsp2_actions_tests/src/runner.rs
  • baml_language/crates/baml_lsp_server/src/lib.rs
  • baml_language/crates/baml_lsp_server/src/lsp_runtime.rs
  • baml_language/crates/baml_lsp_server/src/native_lsp_sender.rs
  • baml_language/crates/baml_lsp_server/src/playground_env.rs
  • baml_language/crates/baml_lsp_server/src/playground_runs.rs
  • baml_language/crates/baml_lsp_server/src/playground_sender.rs
  • baml_language/crates/baml_lsp_server/src/playground_server.rs
  • baml_language/crates/baml_lsp_server/src/playground_session.rs
  • baml_language/crates/baml_lsp_server/src/playground_ws.rs
  • baml_language/crates/baml_project/src/db.rs
  • baml_language/crates/baml_project/src/position.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_engine/tests/collect_tests.rs
  • baml_language/crates/bex_events/src/history/mod.rs
  • baml_language/crates/bex_events/src/run.rs
  • baml_language/crates/bex_project/Cargo.toml
  • baml_language/crates/bex_project/src/bex_lsp/mod.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/diagnostics.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/mod.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/notification.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/wasm_helpers.rs
  • baml_language/crates/bex_project/src/bex_lsp/notification.rs
  • baml_language/crates/bex_project/src/bex_lsp/request.rs
  • baml_language/crates/bex_project/src/lib.rs
  • baml_language/crates/bex_project/src/lsp_ingress.rs
  • baml_language/crates/bex_project/src/project.rs
  • baml_language/crates/bridge_wasm/src/lib.rs
  • baml_language/crates/bridge_wasm/src/wasm_playground.rs
  • baml_language/crates/bridge_wasm/tests/host_callable.rs
  • baml_language/crates/bridge_wasm/tests/wasm_fs_glob.rs
  • typescript2/app-promptfiddle/src/playground/baml-lsp-worker.ts
  • typescript2/app-promptfiddle/src/playground/worker-backend.ts
  • typescript2/app-vscode-ext/src/__tests__/projectRoots.test.ts
  • typescript2/app-vscode-ext/src/extension.ts
  • typescript2/app-vscode-ext/src/panels/WebviewPanel.ts
  • typescript2/app-vscode-ext/src/panels/__tests__/sourcePosition.test.ts
  • typescript2/app-vscode-ext/src/panels/sourcePosition.ts
  • typescript2/app-vscode-ext/src/projectRoots.ts
  • typescript2/app-vscode-webview/src/App.tsx
  • typescript2/app-vscode-webview/src/ExecutionPanel.strict-mode.test.tsx
  • typescript2/pkg-editor/package.json
  • typescript2/pkg-editor/src/MonacoEditor.tsx
  • typescript2/pkg-editor/src/backend.ts
  • typescript2/pkg-playground/package.json
  • typescript2/pkg-playground/src/ExecutionPanel.tsx
  • typescript2/pkg-playground/src/FunctionSidebar.tsx
  • typescript2/pkg-playground/src/execution-store.test.ts
  • typescript2/pkg-playground/src/execution-store.ts
  • typescript2/pkg-playground/src/index.ts
  • typescript2/pkg-playground/src/ports/WebSocketRuntimePort.test.ts
  • typescript2/pkg-playground/src/ports/WebSocketRuntimePort.ts
  • typescript2/pkg-playground/src/ports/WorkerRuntimePort.ts
  • typescript2/pkg-playground/src/project-runtime-state.test.ts
  • typescript2/pkg-playground/src/project-runtime-state.ts
  • typescript2/pkg-playground/src/protocol.test.ts
  • typescript2/pkg-playground/src/protocol.ts
  • typescript2/pkg-playground/src/run-store-client.test.ts
  • typescript2/pkg-playground/src/run-store-client.ts
  • typescript2/pkg-playground/src/runtime-port.ts
  • typescript2/pkg-playground/src/worker-protocol.ts

Comment on lines +51 to +61
#[derive(Debug, Clone)]
enum OutboundPayload {
Message(lsp_server::Message),
RawJson(serde_json::Value),
}

#[derive(Debug, Clone)]
pub(crate) struct OutboundFrame {
payload: OutboundPayload,
_charge: Arc<OutboundCharge>,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Share payload storage across OutboundFrame clones.

Broadcast delivery deep-clones Message/Value, while every clone shares one OutboundCharge. The 64 MiB budget therefore undercounts actual memory by the receiver count. Store the payload in an Arc, or charge each clone independently.

Proposed fix
 pub(crate) struct OutboundFrame {
-    payload: OutboundPayload,
+    payload: Arc<OutboundPayload>,
     _charge: Arc<OutboundCharge>,
 }

-        match &self.payload {
+        match self.payload.as_ref() {

         Ok(OutboundFrame {
-            payload,
+            payload: Arc::new(payload),

Use frame.payload.as_ref() in the writer match as well.

Also applies to: 139-145, 665-669

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_lsp_server/src/lib.rs` around lines 51 - 61, Update
OutboundFrame payload storage so clones share the underlying Message/Value via
Arc instead of deep-cloning payload data while sharing one OutboundCharge;
adjust constructors and all payload accesses, including the writer match, to use
payload.as_ref(), and apply the same change to the additional affected
clone/delivery sites.

Comment on lines +227 to +267
let mut content_length = None;
let mut header = String::new();
loop {
header.clear();
let read = input
.read_line(&mut header)
.map_err(|error| StdioReadError::Framing(error.to_string()))?;
if read == 0 {
return if content_length.is_none() {
Ok(None)
} else {
Err(StdioReadError::Framing(
"unexpected EOF in LSP headers".to_string(),
))
};
}
let Some(line) = header.strip_suffix("\r\n") else {
return Err(StdioReadError::Framing(
"LSP header must end in CRLF".to_string(),
));
};
if line.is_empty() {
break;
}
let Some((name, value)) = line.split_once(": ") else {
return Err(StdioReadError::Framing(format!(
"malformed LSP header: {line}"
)));
};
if name.eq_ignore_ascii_case("Content-Length") {
let parsed = value
.parse::<usize>()
.map_err(|_| StdioReadError::Framing("invalid Content-Length".to_string()))?;
if parsed > MAX_STDIO_FRAME_BYTES {
return Err(StdioReadError::Framing(format!(
"LSP frame exceeds {MAX_STDIO_FRAME_BYTES} bytes"
)));
}
content_length = Some(parsed);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound header reads before allocation.

read_line can grow indefinitely until CRLF arrives, so the 8 MiB body limit does not prevent an oversized or unterminated header from exhausting memory. Use a capped reader and enforce a total header-byte limit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_lsp_server/src/lib.rs` around lines 227 - 267, The
LSP header parsing loop can allocate unbounded memory before validating
Content-Length. In the header-reading logic, replace unbounded read_line usage
with a capped reader or bounded incremental reads, and track cumulative header
bytes against a fixed maximum header-size limit; return StdioReadError::Framing
when the limit is exceeded, including for unterminated headers, while preserving
CRLF and malformed-header validation.

self.close_orphaned_document(&endpoint, &tracking);
}
}
lsp_server::Message::Response(_) => {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Route client responses instead of discarding them.

make_request can emit server-to-client requests, and the scheduler admits client responses, but this branch drops them all. Those requests can never complete. Add response correlation/dispatch to BexLsp, or remove unsupported server-request functionality.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_lsp_server/src/lsp_runtime.rs` at line 492, The
lsp_runtime response branch currently discards all client responses, leaving
requests created by make_request unresolved. Update BexLsp’s message handling
and scheduler dispatch to correlate each lsp_server::Message::Response with its
pending request and deliver the result, or remove the unsupported
server-request/make_request functionality consistently.

Comment on lines +302 to +314
#[derive(Clone)]
pub struct PreparedFunctionRun {
pub source_revision: u64,
pub generation: u64,
pub engine: Arc<dyn crate::Bex>,
}

#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PreparedTestRun {
pub source_revision: u64,
pub generation: u64,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make prepared-run registration RAII-backed.

Preparation registers call_id, but callers only call finish_project_run at the tail of detached tasks. A panic or task abort skips cleanup and can leave runtime/cancellation state pinned. Return a non-Copy lease whose Drop performs cleanup instead of requiring manual pairing.

Based on learnings, registry cleanup should be encapsulated in resource handles’ Drop implementations rather than external cleanup paths.

Also applies to: 406-424

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bex_project/src/bex_lsp/mod.rs` around lines 302 - 314,
Make prepared-run registration RAII-backed by turning PreparedFunctionRun and
PreparedTestRun into non-Copy lease handles that own the registered call_id.
Implement Drop for the handle to invoke the equivalent of finish_project_run and
release runtime/cancellation state, ensuring cleanup on normal completion,
panic, or task abort. Update preparation and detached-task call sites to retain
and drop the lease instead of manually calling finish_project_run.

Source: Learnings

Comment on lines +45 to +61
let document_path = canonical_fs_path_identity(&crate::fs::FsPath::from_vfs(&path));
let document = crate::project::OpenDocument {
client_uri: params.text_document.uri.clone(),
version: params.text_document.version,
text: params.text_document.text.clone(),
};
project_handle.project.apply_all_sources_and_open_document(
&sources,
document_path.clone(),
params.text_document.uri,
params.text_document.version,
params.text_document.text,
);
drop(in_memory_changes);

self.refresh_project(&project_root, ProjectRefreshMode::Full);
self.open_documents
.lock()
.unwrap()
.insert(document_path, document);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve overlays owned by other LSP sessions.

These calls mutate the shared BexProject overlay map, which is keyed only by canonical path. A second connection can overwrite the first connection’s unsaved text, and either connection’s didClose restores disk text while the other editor remains open. (raw.githubusercontent.com)

Include connection/session ownership in the project overlay identity—or retain remaining owners before closing—and add a two-session open/change/close test.

Also applies to: 134-149, 166-176

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bex_project/src/bex_lsp/multi_project/notification.rs`
around lines 45 - 61, Preserve per-session ownership of shared document overlays
in the open-document handling and corresponding change/close paths (including
the regions around the referenced lines). Update the BexProject overlay identity
or ownership tracking so one LSP connection cannot overwrite another session’s
unsaved text or restore disk contents while another session remains open; only
remove/restore an overlay after its final owner closes. Add a test covering two
sessions opening, changing, and closing the same document.

Comment on lines +717 to +721
let forced = MessagePolicy::for_message(&message);
let policy = MessagePolicy {
class: stronger_class(forced.class, requested_policy.class),
cancellation: requested_policy.cancellation,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve forced cancellation behavior for protected methods.

A custom policy can downgrade initialize, shutdown, or workspace/executeCommand from NonCancellableWhenRunning, reopening canceled-response/committed-effect races.

Proposed fix
 let forced = MessagePolicy::for_message(&message);
+let cancellation = if forced.class.is_reserved() {
+    forced.cancellation
+} else {
+    requested_policy.cancellation
+};
 let policy = MessagePolicy {
     class: stronger_class(forced.class, requested_policy.class),
-    cancellation: requested_policy.cancellation,
+    cancellation,
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let forced = MessagePolicy::for_message(&message);
let policy = MessagePolicy {
class: stronger_class(forced.class, requested_policy.class),
cancellation: requested_policy.cancellation,
};
let forced = MessagePolicy::for_message(&message);
let cancellation = if forced.class.is_reserved() {
forced.cancellation
} else {
requested_policy.cancellation
};
let policy = MessagePolicy {
class: stronger_class(forced.class, requested_policy.class),
cancellation,
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/bex_project/src/lsp_ingress.rs` around lines 717 - 721,
Update the policy construction in the message-handling logic to preserve the
forced cancellation setting for protected methods. Combine the forced and
requested cancellation policies using the same precedence rule as class
strength, ensuring custom policies cannot downgrade `initialize`, `shutdown`, or
`workspace/executeCommand` from `NonCancellableWhenRunning`; use the existing
`MessagePolicy::for_message` and `stronger_class` logic as references.

Comment on lines +187 to +190
// Do not expose the runtime port until the worker has created the WASM
// runtime. Otherwise ExecutionPanel's initial catalog-only requestState
// can race initialization and be silently ignored.
await workerReadyPromise;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add timeout and error handling to workerReadyPromise.

The new await workerReadyPromise will hang indefinitely if the worker fails to send ready (e.g., WASM compilation error, network failure fetching the worker script). There is no worker.addEventListener('error', ...) handler and no timeout on the promise, so connect never resolves or rejects.

🛡️ Proposed fix
       const workerReadyPromise = new Promise<void>((resolve, reject) => {
         const onMsg = (event: MessageEvent) => {
           if (event.data?.type === 'ready') {
             worker.removeEventListener('message', onMsg);
             worker.removeEventListener('error', onErr);
             resolve();
           }
         };
+        const onErr = (event: ErrorEvent) => {
+          worker.removeEventListener('message', onMsg);
+          worker.removeEventListener('error', onErr);
+          reject(new Error(`Worker failed to initialize: ${event.message}`));
+        };
         worker.addEventListener('message', onMsg);
+        worker.addEventListener('error', onErr);
+
+        // Timeout to avoid hanging forever if the worker never sends 'ready'
+        setTimeout(() => {
+          worker.removeEventListener('message', onMsg);
+          worker.removeEventListener('error', onErr);
+          reject(new Error('Worker initialization timed out'));
+        }, 30_000);
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript2/app-promptfiddle/src/playground/worker-backend.ts` around lines
187 - 190, Update the worker initialization flow around workerReadyPromise to
reject when the Worker emits an error and to reject after a finite timeout if no
ready message arrives. Ensure connect propagates either failure instead of
hanging, and clean up the error listener and timeout when workerReadyPromise
resolves or rejects.

Comment on lines +596 to +609
for (const { owner, routablePatterns } of desiredRoots.values()) {
try {
await ensureClient(owner, [...routablePatterns.values()]);
} catch (error) {
console.error(`Failed to start BAML language server for ${owner.fsPath}`, error);
}
}

for (const [documentKey, ownerKey] of desiredRoutes) {
if (clients.has(ownerKey)) {
this.documentOwners.set(documentKey, ownerKey);
}
}
updateAggregateServerState();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the error state when all client starts fail.

ensureClient sets error, but the final updateAggregateServerState() sees an empty client map and immediately changes it to stopped, hiding the startup failure.

Proposed fix
+    let clientStartFailed = false;
     for (const { owner, routablePatterns } of desiredRoots.values()) {
       try {
         await ensureClient(owner, [...routablePatterns.values()]);
       } catch (error) {
+        clientStartFailed = true;
         console.error(`Failed to start BAML language server for ${owner.fsPath}`, error);
       }
     }

-    updateAggregateServerState();
+    if (clientStartFailed && clients.size === 0) {
+      updateStatusBar('error');
+    } else {
+      updateAggregateServerState();
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const { owner, routablePatterns } of desiredRoots.values()) {
try {
await ensureClient(owner, [...routablePatterns.values()]);
} catch (error) {
console.error(`Failed to start BAML language server for ${owner.fsPath}`, error);
}
}
for (const [documentKey, ownerKey] of desiredRoutes) {
if (clients.has(ownerKey)) {
this.documentOwners.set(documentKey, ownerKey);
}
}
updateAggregateServerState();
let clientStartFailed = false;
for (const { owner, routablePatterns } of desiredRoots.values()) {
try {
await ensureClient(owner, [...routablePatterns.values()]);
} catch (error) {
clientStartFailed = true;
console.error(`Failed to start BAML language server for ${owner.fsPath}`, error);
}
}
for (const [documentKey, ownerKey] of desiredRoutes) {
if (clients.has(ownerKey)) {
this.documentOwners.set(documentKey, ownerKey);
}
}
if (clientStartFailed && clients.size === 0) {
updateStatusBar('error');
} else {
updateAggregateServerState();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript2/app-vscode-ext/src/extension.ts` around lines 596 - 609, Preserve
the startup error recorded by ensureClient when every client fails to start.
Update the surrounding startup flow and updateAggregateServerState so an empty
clients map does not overwrite an existing error state with stopped; only
transition to stopped when no startup error is present.

Comment on lines +150 to +198
if (msg.type === 'ensureProjectRuntime') {
this.desiredRuntimeLease = msg;
} else if (msg.type === 'releaseProjectRuntime') {
if (
this.desiredRuntimeLease?.project === msg.project &&
this.desiredRuntimeLease.incarnation === msg.incarnation
) {
this.desiredRuntimeLease = null;
}
}

// Every successful open issues one forced state request after the socket
// subscription exists, so pre-open callers do not need to accumulate
// duplicate requestState frames in the reconnect queue.
if (
msg.type === 'requestState' &&
(!this.ws || this.ws.readyState !== WebSocket.OPEN)
) {
return;
}

const serverMsg = this.toServer(msg);
if (!serverMsg) return;
this.sendServerMessage(serverMsg);
}

private sendServerMessage(serverMsg: WebSocketInMessage): void {
const raw = JSON.stringify(serverMsg);
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(raw);
} else {
this.outQueue.push(raw);
// Lease controls describe desired session state, so stale transitions must
// never accumulate in the generic reconnect queue. `connect()` restores
// exactly the latest lease after requesting the catalog.
if (
(msg.type === 'ensureProjectRuntime' ||
msg.type === 'releaseProjectRuntime' ||
msg.type === 'retryProjectRuntime') &&
(!this.ws ||
this.ws.readyState !== WebSocket.OPEN ||
this.runtimeDemandSupported !== true)
) {
return;
}
// Commands are session-scoped. Never queue them across a disconnect, and
// do not let them race the hello that establishes the new session. The
// catalog-only requestState frame is the sole pre-handshake exception.
if (
!this.ws ||
this.ws.readyState !== WebSocket.OPEN ||
(!this.handshakeComplete && serverMsg.type !== 'requestState') ||
(this.handshakeComplete && !this.playgroundCompatible)
) {
return;
}
this.ws.send(JSON.stringify(serverMsg));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve and replay the desired runtime lease after hello.

An ensureProjectRuntime received before handshake is saved at Line 151 but intentionally not sent; Line 419 then deletes the only copy. The same loss occurs across reconnects. Preserve the latest lease and send it exactly once after a compatible hello confirms runtime-demand support.

Also applies to: 419-443

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript2/pkg-playground/src/ports/WebSocketRuntimePort.ts` around lines
150 - 198, Preserve the latest desired runtime lease across pre-handshake sends
and reconnects instead of deleting it in connect() or hello processing. Update
the hello-success path to replay desiredRuntimeLease exactly once after
confirming playground compatibility and runtimeDemandSupported, then clear or
mark it as sent only after transmission. Ensure ensureProjectRuntime and
releaseProjectRuntime continue updating the lease while disconnected, without
entering the generic reconnect queue.

Comment on lines +579 to +587
if (raw.sessionEpoch !== this.serverSessionEpoch) return null;
return {
type: 'controlFlowGraphResult',
sessionEpoch: raw.sessionEpoch,
project: raw.project,
projectIncarnation: raw.projectIncarnation,
sourceRevision: raw.sourceRevision,
generation: raw.generation,
derivedEpoch: raw.derivedEpoch,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align controlFlowGraphResult with the server wire contract.

The Rust WsOutMessage::ControlFlowGraphResult currently serializes only functionName and graph. Consequently, raw.sessionEpoch is undefined, this fence always rejects the frame, and all added identity fields are absent. Extend the server variant and its producers with these fields before enforcing this check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript2/pkg-playground/src/ports/WebSocketRuntimePort.ts` around lines
579 - 587, Update the server-side WsOutMessage::ControlFlowGraphResult variant
and every producer to serialize sessionEpoch, project, projectIncarnation,
sourceRevision, generation, derivedEpoch, functionName, and graph; ensure the
TypeScript controlFlowGraphResult handler receives these fields so its
sessionEpoch fence and identity metadata are valid.

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