Skip to content

fix(lsp): crash when client does not send workspaceFolders in InitializeParams#9557

Merged
ematipico merged 5 commits intobiomejs:mainfrom
datalek:fix/lsp-crash-missing-workspace-folders
Mar 23, 2026
Merged

fix(lsp): crash when client does not send workspaceFolders in InitializeParams#9557
ematipico merged 5 commits intobiomejs:mainfrom
datalek:fix/lsp-crash-missing-workspace-folders

Conversation

@datalek
Copy link
Copy Markdown
Contributor

@datalek datalek commented Mar 20, 2026

This PR was co-authored with Claude Code (Anthropic). Claude helped investigate the root cause, write the fix, and draft the PR description.

Summary

Fix LSP server crash when the client sends only rootUri without workspaceFolders in InitializeParams.

The bug

When Biome's LSP server starts, it registers file watchers (for biome.json, .editorconfig, etc.) in setup_capabilities(). There are two code paths:

Path 1: client sends workspaceFolders (works):

if let Some(folders) = self.session.get_workspace_folders() {
    // Uses folder.uri directly — already a proper URI like "file:///Users/foo/project"
    base_uri: OneOf::Left(folder.clone()),
}

VSCode always sends workspaceFolders in the init request, so this path always runs for VSCode users and the bug was never caught.

Path 2: client sends only rootUri (crashes):

} else if let Some(base_path) = self.session.base_path() {
    // base_path() converts rootUri → filesystem path: "/Users/foo/project"
    // Then tries to parse that bare path as a URI — panics
    base_uri: OneOf::Right(Uri::from_str(base_path.as_str()).unwrap()),
}

session.base_path() takes the rootUri (file:///Users/foo/project) and converts it to a filesystem path (/Users/foo/project). Then the code tries to parse /Users/foo/project back as a URI. But a bare filesystem path is not a valid URI; URIs require a scheme like file://. So Uri::from_str fails at the first character, and .unwrap() panics:

Source Location: crates/biome_lsp/src/server.rs:182:90
Thread Name: tokio-runtime-worker
Message: called `Result::unwrap()` on an `Err` value: ParseError { index: 0, kind: UnexpectedChar }

Context

The LSP specification defines workspaceFolders in InitializeParams as optional. rootUri is the primary way to identify the workspace, workspaceFolders was added later for multi-root support.

Emacs lsp-mode only includes workspaceFolders in InitializeParams when the LSP client is registered as multi-root. Clients like lsp-biome don't opt into multi-root, so the init request contains rootUri but no workspaceFolders. This is spec-compliant. VSCode always sends workspaceFolders, so the fallback path was never exercised there.

The first path (with workspaceFolders) uses URI objects directly, which works. The second path (without workspaceFolders) round-trips URI -> filesystem path -> URI, and the conversion back is missing the file:// scheme.

The fix

Instead of the broken round-trip, added session.base_uri() which returns the original rootUri as-is, no conversion needed. This mirrors what the first path already does with folder.uri.

Changes

  • crates/biome_lsp/src/session.rs: Add base_uri() method that returns the original rootUri from the client.
  • crates/biome_lsp/src/server.rs: Use base_uri() for the RelativePattern base URI in the fallback path. Remove unused use std::str::FromStr import.
  • crates/biome_lsp/src/server.tests.rs: Add regression test initialize_without_workspace_folders_does_not_panic that initializes the server with didChangeWatchedFiles.dynamicRegistration: true and workspace_folders: None, then calls initialized to trigger setup_capabilities.

Test Plan

  • Add regression test that reproduces the exact panic (ParseError { index: 0, kind: UnexpectedChar }), fails without the fix, passes with it
  • Manually tested with Emacs lsp-mode + lsp-biome (which does not send workspaceFolders in InitializeParams): editing, saving, and formatting .ts files all work without errors after the fix

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 20, 2026

🦋 Changeset detected

Latest commit: 2a6706e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the A-LSP Area: language server protocol label Mar 20, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

LSP server watched-files setup now uses the client-provided root URI when workspace folders are absent via a new Session::base_uri() method that returns Option<Uri>. The **/biome.{json,jsonc} watcher is registered using base_uri directly. The .editorconfig watcher’s glob is derived with base_path.map_or_else(...), falling back to **/.editorconfig when base_path is missing. A regression test initialize_without_workspace_folders_does_not_panic was added to ensure initializing without workspaceFolders does not cause a panic.

Possibly related PRs

Suggested reviewers

  • dyc3
  • ematipico
  • arendjr
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: addressing an LSP server crash when InitializeParams lack workspaceFolders.
Description check ✅ Passed The description thoroughly explains the bug, root cause, fix, and testing approach—all directly related to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

Tip

You can validate your CodeRabbit configuration file in your editor.

If your editor has YAML language server, you can enable auto-completion and validation by adding # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json at the top of your CodeRabbit configuration file.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In @.changeset/fix-lsp-missing-workspace-folders.md:
- Line 5: Update the changeset file
.changeset/fix-lsp-missing-workspace-folders.md so the first line begins with
the required bug-fix prefix "Fixed [`#NUMBER`](PR link): " followed by the summary
(e.g. "Fixed [`#9557`](https://github.com/biomejs/biome/pull/9557): Fix LSP server
crash when client omits workspaceFolders; use rootUri for file watcher
registration"), replacing NUMBER and link with the actual PR/issue details;
ensure the rest of the message stays intact and follows the project's changeset
formatting convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9c9209f3-7e24-47fa-9c2b-7aa514420cd2

📥 Commits

Reviewing files that changed from the base of the PR and between bddaf78 and 528aaa1.

📒 Files selected for processing (1)
  • .changeset/fix-lsp-missing-workspace-folders.md

Comment thread .changeset/fix-lsp-missing-workspace-folders.md Outdated
@datalek datalek force-pushed the fix/lsp-crash-missing-workspace-folders branch from 528aaa1 to 56528dd Compare March 20, 2026 13:14
@dyc3
Copy link
Copy Markdown
Contributor

dyc3 commented Mar 20, 2026

the changeset references an issue, but the pr description does not mention it

Copy link
Copy Markdown
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

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

I'm not an expert on the LSP server, but tentatively, i'd say it looks good.

Please link to the issue in your pr description so the issue will auto close, eg fixes #9557

Comment thread crates/biome_lsp/src/server.tests.rs
"@biomejs/biome": patch
---

Fixed [#9557](https://github.com/biomejs/biome/pull/9557): LSP server crash when the client does not send `workspaceFolders` in `InitializeParams`. The file watcher registration now uses the original `rootUri` directly instead of round-tripping through a filesystem path.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changesets must be user-facing, not technical. Please reword it so that we explain which use case we fixed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Since I didn't find any issues, I referenced the PR. Let me know if this approach is correct. I followed the suggestion from coderabbitai.

Comment thread crates/biome_lsp/src/server.tests.rs
@datalek datalek requested a review from ematipico March 22, 2026 08:07
datalek and others added 5 commits March 22, 2026 09:08
Before this commit, when the client did not send workspaceFolders
in InitializeParams, setup_capabilities() converted the rootUri
to a filesystem path via base_path(), then tried to parse that
path back as a URI with Uri::from_str().unwrap(). Since a bare
filesystem path is not a valid URI, this panicked with
ParseError { index: 0, kind: UnexpectedChar }.

After this commit, a new base_uri() method returns the original
rootUri directly, avoiding the lossy path-to-URI round-trip.
This matches what the workspaceFolders branch already does with
folder.uri.
@datalek datalek force-pushed the fix/lsp-crash-missing-workspace-folders branch from fefe141 to 2a6706e Compare March 22, 2026 08:08
@ematipico ematipico merged commit 6671ac5 into biomejs:main Mar 23, 2026
13 checks passed
This was referenced Mar 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-LSP Area: language server protocol

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants