2026.04.27: By forge+kimi2.6
Lua LSP Not Auto-Attaching: Root Cause Analysis
Symptom
When opening a .lua file directly from the command line (nvim main.lua), the Lua LSP
(emmylua_ls) did not attach automatically, even after waiting. However, running :e
inside Neovim caused the LSP to attach immediately.
How the Cause Was Found
1. Verified the obvious first
emmylua_lsbinary was present and executable in the nix wrapper PATH.vim.lsp.config('emmylua_ls')was registered with correct settings.vim.lsp.enable('emmylua_ls')was called in the active init file.- Running
nvim main.luain headless mode showed 0 clients attached initially. - Running
:eand then checking showedemmylua_lsattached.
This confirmed the issue was specifically about startup timing, not missing configuration or a broken LSP binary.
2. Isolated the config with a minimal reproducer
Created a minimal init.lua that only did vim.lsp.config(...) + vim.lsp.enable(...)
and tested it with nvim -u /tmp/minimal_init.lua main.lua. In this minimal setup,
the LSP attached correctly.
This proved the issue was caused by something else in the user's full configuration.
3. Bisected the vimscript config
The user's init.lua is generated by home-manager, which sources a large vimscript
block (extraConfig) before running the Lua block (extraLuaConfig).
By sourcing chunks of the vimscript config incrementally, the trigger was isolated to a single line in the vimscript block.
4. Identified the exact trigger
The trigger was syntax on in the vimscript extraConfig.
Root Cause
syntax on sources Neovim's runtime/syntax/syntax.vim, which contains:
" runtime/syntax/syntax.vim:26
filetype on
" ...
" runtime/syntax/syntax.vim:43-44
if !s:did_ft
doautoall filetypedetect BufRead
endif
doautoall filetypedetect BufRead fires FileType for all already-existing
buffers during init.lua sourcing.
Because home-manager places extraConfig (vimscript) before extraLuaConfig
(Lua), the startup sequence becomes:
- Buffer
main.luais created during startup. syntax ontriggersdoautoall filetypedetect BufRead.FileType luafires formain.lua.- The
nvim.lsp.enableautocmd does not exist yet, so nothing attaches. - Later,
vim.lsp.enable('emmylua_ls')runs in the Lua section. runtime/lua/vim/lsp.lua:635checksvim.v.vim_did_enter == 1. It is 0 during init, so thedoautoallcatch-up is skipped.- The
FileTypeautocmd is created, but only fires for futureFileTypeevents. main.luais permanently missed until:ere-triggersFileType.
Why :e Fixed It
:e re-triggers BufRead -> FileType for the current buffer. By then,
vim.lsp.enable() has already been called, the nvim.lsp.enable autocmd exists,
and the LSP attaches normally.
Why Removing syntax on Is the Correct Fix
Neovim's startup code in src/nvim/main.c:454-462 is explicitly designed to enable
syntax after init.lua finishes:
source_startup_scripts(¶ms);
// ...
filetype_maybe_enable();
// Sources syntax/syntax.vim. We do this *after* the user startup scripts
// so that users can disable syntax highlighting with `:syntax off` if they wish.
syn_maybe_enable();
By putting syntax on in init.lua, you fight against Neovim's design:
- It is redundant — Neovim enables syntax automatically after init.
- It is harmful — it causes premature
FileTypeevents before Lua config runs.
References
runtime/syntax/syntax.vim:26,43-44runtime/lua/vim/lsp.lua:610-637src/nvim/main.c:454-462src/nvim/syntax.c:3195-3202
Note on the "Neovim Bug" Misconception
This behavior is sometimes described as a "Neovim 0.11 bug" (e.g. issue #32910),
but issue #32910 is actually an
unrelated PR about codeAction/resolve support. The real issue here is a
configuration ordering problem caused by syntax on in init, not a bug in
vim.lsp.enable() itself.