Skip to content

lsp--start-workspace sets default-directory without trailing slash #4997

@offby1

Description

@offby1

Thank you for the bug report

  • I am using the latest version of lsp-mode related packages.
  • I checked FAQ and Troubleshooting sections
  • You may also try reproduce the issue using clean environment using the following command: M-x lsp-start-plain

Bug description

The title explains the behavior, but it might not be obvious why that behavior is bad. It's just a minor annoyance -- I was irked when I noticed some of my buffers had a "default-directory" value that didn't end in a slash, and the docstring (and relevant Info node) say that it ends with a slash.

Steps to reproduce

This shell script gets you 95% of the way there; there are some simple steps at the end:

#!/bin/bash
set -e

# Install emacs, npm (for pyright), and git
apt-get update
apt-get install -y emacs-nox npm git

set -x

git config --global user.email "eric.hanchrow@gmail.com"
git config --global user.name "Eric Hanchrow"

# Install pyright globally
npm install -g pyright

# Create a minimal git repo with a Python file
rm -rf /tmp/test-project
mkdir -p /tmp/test-project
cd /tmp/test-project
git init
cat > hello.py << 'PYEOF'
def greet(name: str) -> str:
    return f"Hello, {name}"

print(greet("world"))
PYEOF
git add hello.py
git commit -m "initial"

# Create a minimal emacs init that only loads lsp-mode from MELPA
mkdir -p /tmp/repro-emacs
cat > /tmp/repro-emacs/init.el << 'ELEOF'
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
(package-refresh-contents)
(package-install 'lsp-pyright)

(require 'lsp-mode)
(add-hook 'python-mode-hook #'lsp-deferred)

;; After lsp starts, check default-directory in all buffers
(defun check-default-directories ()
  "Show default-directory for all buffers."
  (interactive)
  (with-current-buffer (get-buffer-create "*dir-check*")
    (erase-buffer)
    (dolist (b (buffer-list))
      (let ((dd (buffer-local-value 'default-directory b)))
        (insert (format "%-40s %s%s\n"
                        (buffer-name b)
                        dd
                        (if (and dd (not (string-suffix-p "/" dd)))
                            "  <-- MISSING SLASH"
                          "")))))
    (display-buffer (current-buffer))))
(global-set-key (kbd "C-c d") #'check-default-directories)
ELEOF

echo ""
echo "===== Setup complete ====="
echo ""
echo "Now run:"
echo "  emacs -Q -l /tmp/repro-emacs/init.el /tmp/test-project/hello.py"
echo ""
echo "Wait for lsp-mode to start (watch the modeline), then press C-c d"
echo "to see which buffers have default-directory without a trailing slash."

When I followed the steps, I indeed saw

File Edit Options Buffers Tools Python Flymake Help
>  hello.py >  greet
  def greet(name: str) -> str:
      return f"Hello, {name}"

  print(greet("world"))










-UU-:%%-  F1  hello.py       All   L2     (Python Flymake[0 0] LSP[pyright:9505] ElDoc) -------------------------------------
 *tar-data  *temp*-97078*                ~/.emacs.d/elpa/
repro-emacs                              /tmp/repro-emacs/
*lsp-log*                                /tmp/test-project/
*pyright::stderr*                        /tmp/test-project  <-- MISSING SLASH
*pyright*                                /tmp/test-project  <-- MISSING SLASH
*Flymake log*                            /tmp/test-project/
*dir-check*                              /tmp/test-project/

Expected behavior

all buffers should have a "default-directory" variable whose last character is a slash.

Which Language Server did you use?

lsp-python with pyright

OS

Linux

Error callstack

Anything else?

The fix is a one-liner, although I'll be happy to make a pull request if that's how you guys roll:

From 6a974390690e2d7ef1823b1272f20e4d29a37d83 Mon Sep 17 00:00:00 2001
From: Eric Hanchrow <eric.hanchrow@gmail.com>
Date: Mon, 16 Feb 2026 09:46:48 -0800
Subject: [PATCH] lsp--start-workspace: ensure default-directory ends with a
 slash.

---
 lsp-mode.el | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lsp-mode.el b/lsp-mode.el
index 48a4184188..56a181b795 100644
--- a/lsp-mode.el
+++ b/lsp-mode.el
@@ -8064,7 +8064,7 @@ should return the command to start the LS server."
 INITIALIZATION-OPTIONS are passed to initialize function.
 SESSION is the active session."
   (lsp--spinner-start)
-  (-let* ((default-directory root)
+  (-let* ((default-directory (file-name-as-directory root))
           (client (copy-lsp--client client-template))
           (workspace (make-lsp--workspace
                       :root root
-- 
2.50.1 (Apple Git-155)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions