Skip to content

Fix stale Java diagnostics after MBT import - #8689

Open
zielinsky wants to merge 13 commits into
scalameta:main-v2from
zielinsky:fix-mbt-java-diagnostics
Open

Fix stale Java diagnostics after MBT import#8689
zielinsky wants to merge 13 commits into
scalameta:main-v2from
zielinsky:fix-mbt-java-diagnostics

Conversation

@zielinsky

@zielinsky zielinsky commented Jul 15, 2026

Copy link
Copy Markdown
Member

Fixes #8686

Summary by CodeRabbit

  • Bug Fixes

    • Prevented outdated compiler diagnostics from appearing after cancellations, restarts, or rapid edits.
    • Improved reliability when multiple compilations run concurrently, ensuring only current results are applied.
    • Fixed Java compiler isolation issues that could affect concurrent workspaces.
    • Improved MBT manual import and workspace reload behavior, including refreshed diagnostics after indexing.
  • Tests

    • Added coverage for compiler races, MBT manual imports, compilation concurrency, and Java compiler isolation.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e147d194-95e5-45a8-8fe2-aebea962c575

📥 Commits

Reviewing files that changed from the base of the PR and between 79568e2 and 4d75ae1.

📒 Files selected for processing (10)
  • metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
  • metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala
  • mtags-java/src/main/scala/scala/meta/internal/jpc/JavaPruneCompiler.scala
  • tests/unit/src/test/scala/scala/meta/internal/jpc/JavaPruneCompilerConcurrencySuite.scala
  • tests/unit/src/test/scala/scala/meta/internal/metals/CompilersRaceLspSuite.scala
  • tests/unit/src/test/scala/scala/meta/internal/metals/mbt/TurbineCompilerConcurrencySuite.scala
  • tests/unit/src/test/scala/tests/mbt/MbtBuildServerManualImportLspSuite.scala
🚧 Files skipped from review as they are similar to previous changes (7)
  • metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala
  • tests/unit/src/test/scala/scala/meta/internal/jpc/JavaPruneCompilerConcurrencySuite.scala
  • tests/unit/src/test/scala/scala/meta/internal/metals/CompilersRaceLspSuite.scala
  • mtags-java/src/main/scala/scala/meta/internal/jpc/JavaPruneCompiler.scala
  • metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
  • tests/unit/src/test/scala/tests/mbt/MbtBuildServerManualImportLspSuite.scala
  • metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala

📝 Walkthrough

Walkthrough

The change coordinates MBT workspace import, Turbine recompilation, and diagnostic refreshes. It adds generation checks for Turbine and presentation compiler work, isolates Java compiler name tables, and adds concurrency and manual-import tests.

Changes

MBT refresh flow

Layer / File(s) Summary
Generation-aware Turbine compilation
metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala, tests/unit/src/test/scala/scala/meta/internal/metals/mbt/TurbineCompilerConcurrencySuite.scala
Turbine scheduled and manual compilations use generations to suppress stale results. Tests cover cancellation, result publication, and indexing.
Generation-aware presentation diagnostics
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala, tests/unit/src/test/scala/scala/meta/internal/metals/CompilersRaceLspSuite.scala
Presentation compiler cancellation increments a generation. Focus and change diagnostics publish only current-generation results.
Per-compiler Java name tables
mtags-java/src/main/scala/scala/meta/internal/jpc/JavaPruneCompiler.scala, tests/unit/src/test/scala/scala/meta/internal/jpc/JavaPruneCompilerConcurrencySuite.scala
Each JavaPruneCompiler uses its own Names table. The concurrency test verifies that compiler instances use distinct tables.
MBT state and diagnostic refresh
metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
refreshMbtStateAfterIndex() recompiles the Turbine classpath, cancels compilers, resets open-buffer diagnostics, and refreshes diagnostics.
Post-index MBT integration
metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala, metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala, tests/unit/src/test/scala/tests/mbt/MbtBuildServerManualImportLspSuite.scala
MBT workspace reload and post-index refresh use an injected callback. Manual import tests verify that diagnostics clear after MBT connection.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: tgodzik

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ConnectionProvider
  participant MetalsLspService
  participant TurbineCompiler
  participant Compilers

  Client->>ConnectionProvider: Import MBT workspace
  ConnectionProvider->>ConnectionProvider: Reload eligible session
  ConnectionProvider->>MetalsLspService: Invoke refreshMbtStateAfterIndex
  MetalsLspService->>TurbineCompiler: Recompile Turbine classpath
  MetalsLspService->>Compilers: Cancel and refresh diagnostics
  Compilers-->>Client: Publish current diagnostics
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The JavaPruneCompiler and broader compiler concurrency changes are not clearly required by issue #8686. Move unrelated compiler concurrency changes and tests to a separate pull request, unless their direct necessity for the MBT diagnostics fix is documented.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: removing stale Java diagnostics after MBT import.
Linked Issues check ✅ Passed The callback refreshes diagnostics after MBT indexing, addressing stale Java import errors described in issue #8686.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala (1)

702-712: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the isTurbineClasspath check to ensure cache invalidation runs.

By restricting onMbtBuildWritten to only run when Turbine is enabled, you prevent the Java compiler cache invalidation and diagnostics refresh from running during a workspace reload for users without Turbine. Since refreshDiagnosticsAfterMbtBuildUpdate already performs its own internal check for Turbine before recompiling, you can safely remove the outer check here.

Note: You may also consider renaming this method (e.g., to refreshMbtDiagnostics) since it now encapsulates broader cache clearing and refresh logic rather than just the Turbine classpath.

🐛 Proposed fix
     private def refreshMbtTurbineClasspath(
         session: BspSession
     ): Future[Unit] =
-      if (
-        MbtBuildServer.isMbtServer(session.main.name) &&
-        userConfig.javaSymbolLoader.isTurbineClasspath
-      ) {
+      if (MbtBuildServer.isMbtServer(session.main.name)) {
         onMbtBuildWritten(mbtBuild())
       } else {
         Future.unit
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala`
around lines 702 - 712, Update refreshMbtTurbineClasspath to invoke
onMbtBuildWritten(mbtBuild()) whenever the session is an MBT server, removing
the userConfig.javaSymbolLoader.isTurbineClasspath condition. Leave the non-MBT
path returning Future.unit; renaming the method is optional and not required for
this fix.
🧹 Nitpick comments (1)
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala (1)

628-634: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant shutdown() calls on evicted cache entries.

The Guava presentationCompilerCache backing jcache is already configured with a RemovalListener that automatically invokes shutdown() on any removed or evicted MtagsPresentationCompiler. Explicitly calling shutdown() on entries removed via jcache.remove(...) is redundant and can be omitted.

  • metals/src/main/scala/scala/meta/internal/metals/Compilers.scala#L628-L634: Remove the pc.shutdown() loop logic in clearJavaCompilerCache and just rely on jcache.remove(key).
  • metals/src/main/scala/scala/meta/internal/metals/Compilers.scala#L680-L683: Remove the .foreach { compiler => compiler.shutdown() } call block inside restartPresentationCompilers.
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/Compilers.scala` around
lines 628 - 634, Remove explicit compiler shutdown calls because
presentationCompilerCache’s RemovalListener already shuts down evicted entries.
In clearJavaCompilerCache, retain only the jcache.remove(key) operations at
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala:628-634; in
restartPresentationCompilers, remove the compiler.shutdown() foreach block at
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala:680-683.
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`:
- Around line 1164-1178: Remove the second compilers.clearJavaCompilerCache()
and subsequent refreshAllDiagnostics() calls from
refreshDiagnosticsAfterMbtBuildUpdate. Preserve the initial cache clear and
diagnostics refresh sequence after the MBT update and Turbine recompilation.

---

Outside diff comments:
In `@metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala`:
- Around line 702-712: Update refreshMbtTurbineClasspath to invoke
onMbtBuildWritten(mbtBuild()) whenever the session is an MBT server, removing
the userConfig.javaSymbolLoader.isTurbineClasspath condition. Leave the non-MBT
path returning Future.unit; renaming the method is optional and not required for
this fix.

---

Nitpick comments:
In `@metals/src/main/scala/scala/meta/internal/metals/Compilers.scala`:
- Around line 628-634: Remove explicit compiler shutdown calls because
presentationCompilerCache’s RemovalListener already shuts down evicted entries.
In clearJavaCompilerCache, retain only the jcache.remove(key) operations at
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala:628-634; in
restartPresentationCompilers, remove the compiler.shutdown() foreach block at
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala:680-683.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: bb1a5fbd-be72-4c9d-8b45-920d8d732456

📥 Commits

Reviewing files that changed from the base of the PR and between 442339c and 2d8cbc5.

📒 Files selected for processing (5)
  • metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
  • metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/mbt/importer/MbtImport.scala

Comment thread metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala (1)

1236-1241: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Ensure Turbine recompilation and Java compiler cache clearing on mbt.json changes.

When mbt.json is modified directly, the current flow updates the build state and refreshes diagnostics, but misses recompiling the Turbine classpath and clearing the Java compiler cache. This bypasses the fixes added for MBT imports, which can lead to stale Java diagnostics.

Reusing refreshDiagnosticsAfterMbtBuildUpdate after the reconnection ensures that all compiler caches and classpaths are consistently and fully invalidated.

🐛 Proposed fix
       case Some(mbtJsonPath) =>
-        Future {
-          updateMbtBuild(MbtBuild.fromFile(mbtJsonPath.toNIO))
-        }.flatMap(_ => reconnectAfterMbtJsonChange())
-          .flatMap(_ => refreshAllDiagnostics())
+        Future { MbtBuild.fromFile(mbtJsonPath.toNIO) }
+          .flatMap { build =>
+            Future { updateMbtBuild(build) }
+              .flatMap(_ => reconnectAfterMbtJsonChange())
+              .flatMap(_ => refreshDiagnosticsAfterMbtBuildUpdate(build))
+          }
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`
around lines 1236 - 1241, Update the mbt.json change flow after
reconnectAfterMbtJsonChange() to call refreshDiagnosticsAfterMbtBuildUpdate
instead of refreshAllDiagnostics(), preserving the existing updateMbtBuild step
so Turbine recompilation and Java compiler cache clearing use the established
refresh path.
🧹 Nitpick comments (1)
metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala (1)

1180-1188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer Future.traverse over map and Future.sequence.

Using Future.traverse is a more idiomatic and concise way to evaluate a collection of futures in Scala.

♻️ Proposed fix
-    val futures =
-      buffers.open.filter(isIncludedPath).map { path =>
-        for {
-          reportedDiagnostics <- compilers.didFocus(path)
-          _ = diagnostics
-            .publishDiagnosticsNotAdjusted(path, reportedDiagnostics)
-        } yield ()
-      }
-    Future.sequence(futures).map(_ => ())
+    Future
+      .traverse(buffers.open.filter(isIncludedPath)) { path =>
+        for {
+          reportedDiagnostics <- compilers.didFocus(path)
+          _ = diagnostics
+            .publishDiagnosticsNotAdjusted(path, reportedDiagnostics)
+        } yield ()
+      }
+      .map(_ => ())
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`
around lines 1180 - 1188, Update the future aggregation in the diagnostics flow
to use Future.traverse directly on buffers.open.filter(isIncludedPath),
preserving the existing per-path compilers.didFocus and
publishDiagnosticsNotAdjusted operations and the Unit result.
🤖 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.

Outside diff comments:
In `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`:
- Around line 1236-1241: Update the mbt.json change flow after
reconnectAfterMbtJsonChange() to call refreshDiagnosticsAfterMbtBuildUpdate
instead of refreshAllDiagnostics(), preserving the existing updateMbtBuild step
so Turbine recompilation and Java compiler cache clearing use the established
refresh path.

---

Nitpick comments:
In `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`:
- Around line 1180-1188: Update the future aggregation in the diagnostics flow
to use Future.traverse directly on buffers.open.filter(isIncludedPath),
preserving the existing per-path compilers.didFocus and
publishDiagnosticsNotAdjusted operations and the Unit result.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 232410cb-ba1f-4489-be3e-457f636407b6

📥 Commits

Reviewing files that changed from the base of the PR and between 2d8cbc5 and a42d684.

📒 Files selected for processing (1)
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala

@zielinsky zielinsky linked an issue Jul 21, 2026 that may be closed by this pull request
@zielinsky
zielinsky force-pushed the fix-mbt-java-diagnostics branch 3 times, most recently from 01f6f47 to 2d71199 Compare July 21, 2026 14:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala (1)

616-634: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rely on the Guava Cache removal listener for cleanup.

Guava's presentationCompilerCache already configures a synchronous removal listener that automatically calls .shutdown() on the MtagsPresentationCompiler whenever an entry is removed. Explicitly calling shutdown() here is redundant.

Consider simplifying the loop to just remove the keys.

♻️ Proposed refactor
-    for {
-      key <- javaKeys
-      pc <- Option(jcache.remove(key))
-    } {
-      pc.shutdown()
-    }
+    javaKeys.foreach(jcache.remove)
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/Compilers.scala` around
lines 616 - 634, Update clearJavaCompilerCache to remove each matching key from
jcache without capturing the removed compiler or calling pc.shutdown(); rely on
the cache’s synchronous removal listener to perform compiler cleanup. Preserve
the existing Java-key filtering behavior.
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala`:
- Around line 244-274: Prevent duplicate MBT refreshes and ensure diagnostics
refresh regardless of Turbine configuration. In
metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala:244-274,
remove onMbtBuildUpdatedAfterImport and its chaining from runMbtReimport and
reimportMbtAndReload, relying on reloadCurrentSession/importBuildAndIndex;
update forceMbtReimport as needed to use the unconditional import directly. In
710-717, optionally rename refreshMbtTurbineClasspath to refreshMbtState. In
725-735, remove the isTurbineClasspath guard so onMbtBuildUpdated always runs
after BSP indexing.

---

Nitpick comments:
In `@metals/src/main/scala/scala/meta/internal/metals/Compilers.scala`:
- Around line 616-634: Update clearJavaCompilerCache to remove each matching key
from jcache without capturing the removed compiler or calling pc.shutdown();
rely on the cache’s synchronous removal listener to perform compiler cleanup.
Preserve the existing Java-key filtering behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2825638-1ffd-4779-a9f3-a3a3160ed586

📥 Commits

Reviewing files that changed from the base of the PR and between a42d684 and 2d71199.

📒 Files selected for processing (4)
  • metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
  • metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala
🚧 Files skipped from review as they are similar to previous changes (1)
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala

Comment thread metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala Outdated
@zielinsky
zielinsky force-pushed the fix-mbt-java-diagnostics branch from 2d71199 to 755db23 Compare July 21, 2026 19:52
@zielinsky
zielinsky force-pushed the fix-mbt-java-diagnostics branch 2 times, most recently from 26864e9 to 632836e Compare July 21, 2026 19:55
@zielinsky
zielinsky force-pushed the fix-mbt-java-diagnostics branch 3 times, most recently from 97dc22e to 9ef51e3 Compare July 22, 2026 09:40
@zielinsky
zielinsky force-pushed the fix-mbt-java-diagnostics branch from 9ef51e3 to 6675839 Compare July 22, 2026 09:43
@zielinsky
zielinsky marked this pull request as ready for review July 22, 2026 09:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala`:
- Around line 698-703: Update the production branch in importBuildAndIndex’s
refresh handling so it returns the recovered refresh future rather than
discarding refresh.recover’s result and immediately returning
Future.unit.withInterrupt. Preserve the existing warning recovery and interrupt
behavior while ensuring completion awaits the diagnostics refresh.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d52796e7-7bfe-4e43-85f6-1d8de299ee50

📥 Commits

Reviewing files that changed from the base of the PR and between 2d71199 and 6675839.

📒 Files selected for processing (3)
  • metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala

Comment thread metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala Outdated
@zielinsky
zielinsky requested a review from tgodzik July 23, 2026 02:13
Comment thread metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala (1)

288-294: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated reload-then-import sequence.

This mirrors reloadCurrentSession (Line 251-265): session.workspaceReload() followed by connect(new ImportBuildAndIndex(session), progress). Consider extracting a shared private helper (e.g., reloadAndImport(session, progress): Future[BuildChange]) to avoid the two call sites diverging.

♻️ Proposed refactor
+  private def reloadAndImportSession(
+      session: BspSession,
+      progress: TaskProgress,
+  ): Future[BuildChange] =
+    for {
+      _ <- session.workspaceReload()
+      buildChange <- connect(new ImportBuildAndIndex(session), progress)
+    } yield buildChange
+
   def reloadCurrentSession(): Future[Unit] =
     bspSession match {
       case Some(session) if session.canReloadWorkspace =>
         workDoneProgress.trackProgressFuture(
           "Sync",
-          progress =>
-            for {
-              _ <- session.workspaceReload()
-              _ <- connect(new ImportBuildAndIndex(session), progress)
-            } yield (),
+          progress => reloadAndImportSession(session, progress).ignoreValue,
           metricName = Some("reload_build_server"),
         )
       case _ =>
         fullConnect()
     }
         bspSession match {
           case Some(session)
               if MbtBuildServer.isMbtServer(session.main.name) &&
                 session.canReloadWorkspace =>
-            for {
-              _ <- session.workspaceReload()
-              buildChange <- connect(new ImportBuildAndIndex(session), progress)
-            } yield buildChange
+            reloadAndImportSession(session, progress)
           case Some(session) =>
             connect(new ImportBuildAndIndex(session), progress)
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala`
around lines 288 - 294, Extract the duplicated session.workspaceReload()
followed by connect(new ImportBuildAndIndex(session), progress) flow from
reloadCurrentSession and the MbtBuildServer branch into a shared private helper
such as reloadAndImport(session, progress). Replace both call sites with the
helper while preserving their existing Future[BuildChange] behavior.
🤖 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.

Nitpick comments:
In `@metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala`:
- Around line 288-294: Extract the duplicated session.workspaceReload() followed
by connect(new ImportBuildAndIndex(session), progress) flow from
reloadCurrentSession and the MbtBuildServer branch into a shared private helper
such as reloadAndImport(session, progress). Replace both call sites with the
helper while preserving their existing Future[BuildChange] behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 402e0cce-1828-49df-bdf2-7a7d5eb8baab

📥 Commits

Reviewing files that changed from the base of the PR and between 6675839 and be338ce.

📒 Files selected for processing (4)
  • metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala
  • tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala
🚧 Files skipped from review as they are similar to previous changes (1)
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala (1)

987-998: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate userConfig override.

This is nearly identical to MbtBuildServerLspSuite.userConfig (lines 46-58), differing only in automaticImportBuild and the absence of testUserInterface. Extracting a shared base trait/class with the common config would prevent the two configs from silently drifting apart.

♻️ Proposed refactor sketch
+trait MbtBuildServerBaseUserConfig { self: BaseCompletionLspSuite =>
+  protected def baseUserConfig: UserConfiguration =
+    self.userConfig.copy(
+      fallbackScalaVersion = Some(BuildInfo.scalaVersion),
+      presentationCompilerDiagnostics = true,
+      buildOnChange = false,
+      buildOnFocus = false,
+      workspaceSymbolProvider = WorkspaceSymbolProviderConfig.mbt,
+      referenceProvider = ReferenceProviderConfig.mbt,
+      fallbackSourcepath = FallbackSourcepathConfig("all-sources"),
+      preferredBuildServer = Some(MbtBuildServer.name),
+    )
+}
+
 class MbtBuildServerManualImportLspSuite
-    extends BaseCompletionLspSuite("mbt-build-server-manual-import") {
+    extends BaseCompletionLspSuite("mbt-build-server-manual-import")
+    with MbtBuildServerBaseUserConfig {

   override def userConfig: UserConfiguration =
-    super.userConfig.copy(
-      fallbackScalaVersion = Some(BuildInfo.scalaVersion),
-      presentationCompilerDiagnostics = true,
-      buildOnChange = false,
-      buildOnFocus = false,
-      workspaceSymbolProvider = WorkspaceSymbolProviderConfig.mbt,
-      referenceProvider = ReferenceProviderConfig.mbt,
-      fallbackSourcepath = FallbackSourcepathConfig("all-sources"),
-      preferredBuildServer = Some(MbtBuildServer.name),
-      automaticImportBuild = AutoImportBuildKind.Off,
-    )
+    baseUserConfig.copy(automaticImportBuild = AutoImportBuildKind.Off)
🤖 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 `@tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala` around
lines 987 - 998, Remove the duplicated userConfig definition in
MbtBuildServerLspSuite by extracting the shared configuration into a reusable
base trait or class, then override only the suite-specific automaticImportBuild
and testUserInterface differences. Ensure both suites continue using the common
fallback, provider, build, and preferred server settings.
🤖 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.

Nitpick comments:
In `@tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala`:
- Around line 987-998: Remove the duplicated userConfig definition in
MbtBuildServerLspSuite by extracting the shared configuration into a reusable
base trait or class, then override only the suite-specific automaticImportBuild
and testUserInterface differences. Ensure both suites continue using the common
fallback, provider, build, and preferred server settings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9dd4dbd6-80b8-47cd-9df2-c6e526feef5f

📥 Commits

Reviewing files that changed from the base of the PR and between 6675839 and 8ec85ae.

📒 Files selected for processing (4)
  • metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala
  • tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala
🚧 Files skipped from review as they are similar to previous changes (3)
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala
  • metals/src/main/scala/scala/meta/internal/metals/ConnectionProvider.scala

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (3)
metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala (1)

244-249: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mark source-path entries after a forced compile.

compileNow() passes the default empty markCompiled list, so cleanup() cannot remove any source-path entries. They continue to be served from SOURCE_PATH after the forced Turbine rebuild.

Proposed fix
 def compileNow(): Future[TurbineCompileResult] = {
+  val toCompile = sourcepathSources()
   val generation = compileGeneration.incrementAndGet()
   doCompile.cancelAll()
   Future {
-    doCompileNow(expectedGeneration = generation)
+    doCompileNow(
+      expectedGeneration = generation,
+      markCompiled = toCompile,
+    )
   }
 }
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala`
around lines 244 - 249, Update compileNow() to pass the source-path entries
requiring cleanup to doCompileNow via its markCompiled parameter, rather than
the default empty list. Ensure the forced Turbine compilation marks those
entries so cleanup() removes them from SOURCE_PATH after rebuilding.
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala (1)

406-431: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Release completed in-flight tokens.

inFlightDidChange is never cleaned up, so every distinct edited path remains retained for the server lifetime. Remove only the matching token when its request completes.

Proposed fix
               timerProvider
                 .withTimer(
                   "computed diagnostics",
                   reportStatus = false,
                   onlyIf = false,
                 ) {
                   pc.didChange(params).asScala
                 }
+                .andThen { case _ =>
+                  inFlightDidChange.remove(file, token)
+                }
                 .map { case (timer, reportedDiagnostics) =>
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/Compilers.scala` around
lines 406 - 431, Update the request completion handling in fileDidChange so the
corresponding entry is removed from inFlightDidChange when its token completes.
Remove it only if the stored token still matches the completed request’s token,
preserving newer in-flight requests for the same path.
metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala (1)

1232-1240: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restart Java compilers for watched proto deletions too.

paths excludes delete events, so deleting a .proto only reaches onDelete; this branch never restarts Java presentation compilers. Cached generated symbols can therefore survive a watched proto deletion.

Proposed fix
     if (paths.exists(_.isProtoFilename)) {
       paths.filter(_.isProtoFilename).foreach(mbt2.didSave)
-      compilers.restartJavaCompilers()
     }
+    if (
+      paths.exists(_.isProtoFilename) ||
+      otherDeleteEvents.exists(_.getUri().toAbsolutePath.isProtoFilename)
+    )
+      compilers.restartJavaCompilers()

Based on the PR objective to invalidate stale diagnostics after MBT/proto changes.

🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`
around lines 1232 - 1240, Extend the proto-change handling in the visible Metals
LSP service flow to also restart Java presentation compilers when a watched
proto deletion is processed through onDelete, since deleted paths are absent
from paths. Reuse the existing proto filename detection and
compilers.restartJavaCompilers() behavior, while preserving the current didSave
handling for existing proto paths.
🤖 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.

Outside diff comments:
In `@metals/src/main/scala/scala/meta/internal/metals/Compilers.scala`:
- Around line 406-431: Update the request completion handling in fileDidChange
so the corresponding entry is removed from inFlightDidChange when its token
completes. Remove it only if the stored token still matches the completed
request’s token, preserving newer in-flight requests for the same path.

In `@metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala`:
- Around line 244-249: Update compileNow() to pass the source-path entries
requiring cleanup to doCompileNow via its markCompiled parameter, rather than
the default empty list. Ensure the forced Turbine compilation marks those
entries so cleanup() removes them from SOURCE_PATH after rebuilding.

In `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`:
- Around line 1232-1240: Extend the proto-change handling in the visible Metals
LSP service flow to also restart Java presentation compilers when a watched
proto deletion is processed through onDelete, since deleted paths are absent
from paths. Reuse the existing proto filename detection and
compilers.restartJavaCompilers() behavior, while preserving the current didSave
handling for existing proto paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 54220ca3-646b-4a2a-a2d0-b70aa263b850

📥 Commits

Reviewing files that changed from the base of the PR and between 8ec85ae and 155bba8.

📒 Files selected for processing (5)
  • metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala
  • tests/unit/src/test/scala/scala/meta/internal/metals/CompilersRaceLspSuite.scala
  • tests/unit/src/test/scala/tests/mbt/MbtBuildServerManualImportLspSuite.scala

@zielinsky
zielinsky force-pushed the fix-mbt-java-diagnostics branch from 155bba8 to ce50ab3 Compare July 30, 2026 13:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala (2)

1153-1164: 🎯 Functional Correctness | 🟠 Major

Verify concurrent-recompile safety of recompileTurbineClasspath().

A prior reviewer (tgodzik) flagged this exact pattern on an earlier commit: "The problem here is that we can't run the recompile if the turbine compilation is already running. We should cancel it and run again." The current refreshMbtStateAfterIndex still calls mbt2.recompileTurbineClasspath() unconditionally with no visible cancellation of an in-flight compile. This PR's stack description claims TurbineCompiler.scala (not in this review batch) now does generation-aware suppression of stale results, which may address the correctness half of this concern, but doesn't necessarily avoid wasted duplicate compilation work when refreshMbtStateAfterIndex runs while a manual/previous recompile is still in progress.

Please confirm whether the generation tracking in TurbineCompiler.scala makes concurrent recompileTurbineClasspath() calls safe (correct + non-wasteful), or whether this call site still needs to cancel an in-flight compile first.

🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`
around lines 1153 - 1164, Verify the concurrency contract of
mbt2.recompileTurbineClasspath() in refreshMbtStateAfterIndex: ensure generation
tracking prevents stale results and avoids duplicate work when a compile is
already running. If it does not, cancel the in-flight Turbine compilation before
starting the refresh, using the existing cancellation mechanism, while
preserving the subsequent compiler cancellation, diagnostics reset, and refresh
flow.

1284-1309: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

onDelete's proto-triggered Java compiler restart is fire-and-forget and never refreshes diagnostics.

Unlike didSave (Line 1128-1139), which restarts Java compilers on proto save and then unconditionally awaits refreshAllDiagnostics(), onDelete restarts Java compilers via .map { _ => if (path.isProtoFilename) compilers.restartJavaCompilers() } without chaining/awaiting the restart and without any subsequent diagnostics refresh. Open Java files depending on a deleted proto will keep showing stale (clean) diagnostics until the user manually refocuses them — which is exactly the class of bug this PR is meant to fix. The companion test in ProtoPCJavaSuite.scala works around this by sleeping 500ms and manually refocusing (see linked comment there).

🐛 Proposed fix
       .ignoreValue
-      .map { _ =>
-        if (path.isProtoFilename) compilers.restartJavaCompilers()
-      }
+      .flatMap { _ =>
+        if (path.isProtoFilename) {
+          compilers.restartJavaCompilers()
+          refreshAllDiagnostics()
+        } else Future.successful(())
+      }
🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`
around lines 1284 - 1309, Update onDelete to chain and await the proto-triggered
compilers.restartJavaCompilers() operation rather than invoking it
fire-and-forget, then invoke and await refreshAllDiagnostics() after the
restart, matching the didSave flow while preserving the existing behavior for
non-proto deletions.
🧹 Nitpick comments (2)
metals/src/main/scala/scala/meta/internal/metals/Compilers.scala (1)

358-403: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Optional: consider real cancellation instead of compute-then-discard for stale didFocus retries.

fileDidChange cancels superseded in-flight compiler work via a real CompletableCancelToken, whereas didFocus always lets the (possibly now-irrelevant) computation finish before detecting staleness and retrying. Wiring didFocus into the same in-flight-cancellation mechanism would avoid wasted presentation-compiler work during rapid focus/cancel churn, though this is a performance nicety rather than a correctness gap given the retry-once-then-Nil fallback already prevents publishing stale results.

🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/Compilers.scala` around
lines 358 - 403, Optionally update didFocus to use the same in-flight
cancellation mechanism as fileDidChange, replacing EmptyCancelToken with a real
CompletableCancelToken tied to superseded focus computations. Cancel the prior
didFocus work when a newer generation starts, while preserving the existing
generation check, single retry, and Nil fallback behavior.
metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala (1)

160-253: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Consider dedicated concurrency test coverage for TurbineCompiler's generation tracking.

This cohort adds CompilersRaceLspSuite.scala to directly exercise the new generation-based staleness handling in Compilers.scala, but no analogous focused test targets the new compileGeneration/compileLock logic in TurbineCompiler. Given the subtlety of the double-checked-locking pattern here, a small unit test simulating overlapping compileNow()/scheduleCompile() calls (with a controllable Sleeper) would guard against regressions.

🤖 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 `@metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala`
around lines 160 - 253, Add focused concurrency test coverage for
TurbineCompiler’s compileGeneration and compileLock behavior, using a
controllable Sleeper to overlap compileNow() and scheduleCompile() calls. Verify
stale generations do not publish results while the current generation does, and
preserve the existing compilation behavior for non-overlapping calls.
🤖 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 `@tests/unit/src/test/scala/tests/p/ProtoPCJavaSuite.scala`:
- Around line 1451-1492: Update the delete-handling flow in onDelete so the
Java-compiler restart and subsequent diagnostics refresh are awaited before its
returned future completes. Then simplify proto-delete-invalidates-java by
removing server.waitFor(500) and the manual server.didFocus(java), asserting the
expected diagnostic directly after didChangeWatchedFiles completes.

---

Outside diff comments:
In `@metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala`:
- Around line 1153-1164: Verify the concurrency contract of
mbt2.recompileTurbineClasspath() in refreshMbtStateAfterIndex: ensure generation
tracking prevents stale results and avoids duplicate work when a compile is
already running. If it does not, cancel the in-flight Turbine compilation before
starting the refresh, using the existing cancellation mechanism, while
preserving the subsequent compiler cancellation, diagnostics reset, and refresh
flow.
- Around line 1284-1309: Update onDelete to chain and await the proto-triggered
compilers.restartJavaCompilers() operation rather than invoking it
fire-and-forget, then invoke and await refreshAllDiagnostics() after the
restart, matching the didSave flow while preserving the existing behavior for
non-proto deletions.

---

Nitpick comments:
In `@metals/src/main/scala/scala/meta/internal/metals/Compilers.scala`:
- Around line 358-403: Optionally update didFocus to use the same in-flight
cancellation mechanism as fileDidChange, replacing EmptyCancelToken with a real
CompletableCancelToken tied to superseded focus computations. Cancel the prior
didFocus work when a newer generation starts, while preserving the existing
generation check, single retry, and Nil fallback behavior.

In `@metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala`:
- Around line 160-253: Add focused concurrency test coverage for
TurbineCompiler’s compileGeneration and compileLock behavior, using a
controllable Sleeper to overlap compileNow() and scheduleCompile() calls. Verify
stale generations do not publish results while the current generation does, and
preserve the existing compilation behavior for non-overlapping calls.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: df63659b-ba45-4160-bb1a-c49897576315

📥 Commits

Reviewing files that changed from the base of the PR and between 155bba8 and f5e9043.

📒 Files selected for processing (8)
  • metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
  • metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/mbt/TurbineCompiler.scala
  • mtags-java/src/main/scala/scala/meta/internal/jpc/JavaPruneCompiler.scala
  • tests/unit/src/test/scala/scala/meta/internal/jpc/JavaPruneCompilerConcurrencySuite.scala
  • tests/unit/src/test/scala/scala/meta/internal/metals/CompilersRaceLspSuite.scala
  • tests/unit/src/test/scala/tests/mbt/MbtBuildServerManualImportLspSuite.scala
  • tests/unit/src/test/scala/tests/p/ProtoPCJavaSuite.scala

Comment thread tests/unit/src/test/scala/tests/p/ProtoPCJavaSuite.scala Outdated
@zielinsky
zielinsky force-pushed the fix-mbt-java-diagnostics branch from f5e9043 to 5ec1c86 Compare July 31, 2026 08:09
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

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.

Some Times Errors don't Go Away after Import

2 participants