Skip to content

LSP Logs filled with 'ERR Error in tick msg="key not found:..."' #420

@martin-c

Description

@martin-c

Summary

When removeIdleNimsuggests runs against an idle project whose nimsuggest's
openFiles still references a URI that has already been evicted from
ls.openFiles, the unchecked indexing ls.openFiles[uri] raises KeyError.
The exception is caught by tick's outer try/except, but the cleanup
sequence (project.stop() and ls.projectFiles.del(project.file)) never runs,
so the same stale project is re-selected for idle removal on every subsequent
tick — producing infinite log spam at the configured tick interval.

Versions

  • nimlangserver 1.14.0
  • Nim 2.2.8 (MacOSX amd64, via Rosetta on Apple Silicon)
  • VS Code / Cursor 1.105.1 with nimlang.nimlang 1.8.1

Reproduction

  1. Open any .nim file outside the workspace (e.g. /tmp/scratch.nim) that
    does not belong to a project. The extension/server registers a per-file
    nimsuggest project for it.
  2. Either delete the file from disk, or close the editor tab so its URI is
    removed from ls.openFiles while the nimsuggest itself still tracks it.
  3. Wait nimsuggestIdleTimeout ms (default 120000) for the idle-cleanup tick.

Observed output (repeats indefinitely, once per tick):

DBG Removing idle nimsuggest                   project=/tmp/scratch.nim
DBG Removing idle nimsuggest open file         uri=file:///tmp/scratch.nim
ERR Error in tick                              msg="key not found: file:///tmp/scratch.nim"
An exception occured
key not found: file:///tmp/scratch.nim
tables.nim(235)          raiseKeyError
ls.nim(1323)             tick

Root cause

ls.nim removeIdleNimsuggests:

for uri in ns.openFiles:
  debug "Removing idle nimsuggest open file", uri = uri
  await ls.makeIdleFile(ls.openFiles[uri])   # <-- KeyError when uri ∉ ls.openFiles
project.stop()
ls.projectFiles.del(project.file)

KeyError aborts the for loop on the first orphan URI, so project.stop()
and ls.projectFiles.del(project.file) are never reached. The outer try in
tick swallows the exception, the project stays in ls.projectFiles, and the
next tick repeats the whole sequence.

Suggested fix

Skip URIs that no longer have a corresponding NlsFileInfo rather than
indexing blindly, e.g.:

for uri in ns.openFiles:
  debug "Removing idle nimsuggest open file", uri = uri
  ls.openFiles.withValue(uri, info):
    await ls.makeIdleFile(info[])
project.stop()
ls.projectFiles.del(project.file)

…or getOrDefault + nil guard. The important property is that
ls.projectFiles.del(project.file) is always reached so the same idle project
is not reconsidered on the next tick.

2026-05-16_langserver_openfiles.patch

Status

Patch built and compiled against 1.14.0 (Nim 2.0.8 / 2.2.8); rebased onto and
verified to apply cleanly on current upstream master (8f0786b). Non-fatal bug:
tick's outer try/except already prevents a crash, so the symptom is log spam
plus a leaked idle project (its nimsuggest is never stopped, ls.projectFiles
keeps the stale entry). After the fix the orphan URI is skipped, project.stop()
/ ls.projectFiles.del run, and the project is removed once with the normal
"Nimsuggest for … was stopped because it was idle for too long" message — no
repeat on the next tick.

How to reproduce / verify quickly

Lower the idle timeout so a tick fires in seconds instead of 2 min — global Zed
lsp.nim.settings (or VS Code nim.nimsuggestIdleTimeout):
"nimsuggestIdleTimeout": 3000. Then open a project-less scratch file
(/tmp/scratch.nim), delete it / close the tab so its URI leaves ls.openFiles
while the nimsuggest still tracks it, and watch the LSP log: pre-patch =
ERR Error in tick msg="key not found: …" every tick; post-patch = a single
clean stop, then quiet. (Must be a non-entry-point nimsuggest — removeIdleNimsuggests
skips ls.entryPoints.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions