Skip to content

Scala: Metals' build-import prompt is never answered, so a project that was not imported by another editor never gets a build server #1768

Description

@merlinorg

Preconditions:

  • I have made sure it's an actual issue, not a question (use GitHub Discussions instead).
  • I have consulted the user guide and verified that the issue cannot be resolved by adjusting configuration/following recommended workflows.
  • I have looked for similar issues and discussions, including closed ones.

Issue details:

  • I have provided a meaningful title and description.
  • I have explained how the issue arose and, where possible, added instructions on how to reproduce it.
  • I have added details on my setup: Serena version, MCP client, OS, the programming language(s), relevant configuration adjustments and project specifics.
  • If the issue relates to an application of Serena to an open-source project, I have added the link.

Summary

Metals asks, via window/showMessageRequest, whether to import a workspace it has not seen before. Serena registers no handler for that request, so it comes back as MethodNotFound, Metals logs Unexpected error initializing server and imports nothing. The project then has no build server and no build target, and every cross-file query is answered by the fallback presentation compiler, which sees one file at a time — so find_referencing_symbols returns nothing, with no error at Serena's level to say why.

This is not a monorepo edge case. It is any Scala project that has not already been imported by something else. The Scala setup guide's Quick Start ("open your project in VS Code, accept Import build…") is a workaround for it, and simply doesn't work for many workflows (multiple independent worktrees, devcontainers).

Setup

  • Serena main @ 9a9d07e83d8c1cba3458992707f440c624446c6d
  • Metals 1.6.4 (Serena's bundled default), Scala 3.3.6, sbt 1.11.7 (pinned by the fixture)
  • OpenJDK 25.0.3, Python 3.14.6, Debian 13 (aarch64)
  • No MCP client involved — driven directly through SolidLanguageServer
  • No configuration adjustments

Reproduction

Save as sscce.py at the root of a Serena checkout, then (uv sync --all-extras first if needed):

uv run python sscce.py /tmp/plain

Needs java, sbt and cs on PATH. It writes a perfectly ordinary single-module sbt project — no .bloop, no .bsp, nothing pre-imported — and asks for the references to Lib.greet, retrying for three minutes. Note that on a working Serena this causes Metals to run sbt bloopInstall and fetch Scala 3.3.6, so run it somewhere that is fine.

"""Serena + Metals on a single-root sbt project that has never been imported."""

import os
import shutil
import sys
import time

from solidlsp.language_servers.scala_language_server import ScalaLanguageServer
from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
from solidlsp.settings import SolidLSPSettings

REPO = os.path.abspath(sys.argv[1])

# --- fixture: an ordinary sbt project at the repository root, no .bloop/.bsp ------------
shutil.rmtree(REPO, ignore_errors=True)
files = {
    "build.sbt": 'ThisBuild / scalaVersion := "3.3.6"\n\nlazy val plain = (project in file("."))\n',
    "project/build.properties": "sbt.version=1.11.7\n",
    "src/main/scala/plain/Lib.scala": 'package plain\n\nobject Lib {\n  def greet(who: String): String = s"hello $who"\n}\n',
    "src/main/scala/plain/App.scala": 'package plain\n\nobject App {\n  def main(args: Array[String]): Unit = println(Lib.greet("world"))\n}\n',
}
for rel, content in files.items():
    path = os.path.join(REPO, rel)
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w") as f:
        f.write(content)

ls = ScalaLanguageServer(LanguageServerConfig(ls_id=LanguageServerId.SCALA), REPO, SolidLSPSettings())

with ls.start_server_context():
    refs = []
    deadline = time.time() + 180
    while time.time() < deadline:
        # Lib.scala line 3 (0-based) is `  def greet(...)`; column 6 is on `greet`.
        refs = ls.request_references(os.path.join("src", "main", "scala", "plain", "Lib.scala"), 3, 6)
        if refs:
            break
        time.sleep(10)
    print("references to Lib.greet:", [r["relativePath"] for r in refs])

Expected

references to Lib.greet: ['src/main/scala/plain/App.scala']

Actual

references to Lib.greet: []

/tmp/plain/.metals/metals.log:

INFO  Started: Metals version 1.6.4 in folders '/tmp/plain' for client Serena .
ERROR Unexpected error initializing server: 
org.eclipse.lsp4j.jsonrpc.ResponseErrorException: method 'window/showMessageRequest' not handled on client.
	at org.eclipse.lsp4j.jsonrpc.RemoteEndpoint.handleResponse(RemoteEndpoint.java:220)
	at org.eclipse.lsp4j.jsonrpc.RemoteEndpoint.consume(RemoteEndpoint.java:204)
	…
INFO  no build target found for /tmp/plain/src/main/scala/plain/Lib.scala. Using presentation compiler with project's scala-library version: 3.3.6

(Stack trace trimmed.) That last line then repeats for every attempt, for the full three minutes. There is no build server: no Connected to Build server, no .bloop/ produced.

The question Metals was asking is Messages.ImportBuild: "New sbt workspace detected, would you like to import the build?", offering Import build / Not now / Don't show again.

Analysis

Serena answers only the server-to-client requests a language server has registered a handler for; anything else is failed back as MethodNotFound (src/solidlsp/ls_process.py:433-442). ScalaLanguageServer._start_server registers none, so Metals' prompt is one of them. There is precedent for handling it elsewhere in the codebase — ansible_language_server.py:308 registers exactly this handler, added in #1170, for exactly this reason ("without this handler the client replies with MethodNotFound, which the ansible LS treats as fatal").

Declining the question rather than answering it would not help. There is a switch for it — disableShowMessageRequest, read from the metals.disable-show-message-request system property (MetalsServerConfig.scala:147-150, binaryOption at :193) — and since Serena launches the Metals JVM it could set it, though not over LSP, as it is not a client capability. But where it is set, ConfiguredLanguageClient.showMessageRequest (:52-64) answers with the prompt's own default, and for this prompt that is Not now (BloopInstall.scala:190-205): no import either. Answering is the only route from a fresh checkout to a build server.

Nor is there a Serena-side setting that helps: the workaround is to import the build with another tool first, which is what the setup guide's Quick Start describes. Once .bloop/ exists Metals auto-connects and never asks, which is presumably why this has gone unreported — the documented workflow steps around it.

(Metals references are at scalameta/metals @ 987b0cdfa11905abeee575de0968293f9837936e, under metals/src/main/scala/scala/meta/internal/: metals/Messages.scala:207-233 for the prompt, builds/BloopInstall.scala:174-213 for the request and how the answer is compared against ImportBuild.yes, metals/clients/language/ConfiguredLanguageClient.scala:52-64 and metals/MetalsServerConfig.scala:147-150 for the fallback.)

Related

Fix

PR incoming. Registers the handler on the Scala server and answer the three prompts that lead to a build server — Import build, Import changes, Connect — dismissing anything else with null.

With it, the script above prints the expected line, and the log shows time: ran 'sbt bloopInstall' in 5.54s followed by Connected to Build server: Bloop v2.0.17.


Drafted with the help of Claude Code. I reproduced the bug myself, verified the source citations and the log output, and edited the text; questions are mine to answer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions