2026-07-02 Neovim 0.12 Config Fixes and lua_ls Split-Root

What I set out to do

Started as a plain question — what shipped in Neovim 0.11 → 0.12.3 — but I’m already running 0.12.3, so it turned into auditing my nvim config against the 0.12 breaking changes, then fixing whatever :checkhealth flagged, wiring up the dap_repl parser, and finally root-causing the recurring “undefined global vim” diagnostics.

What I actually did

  • Real latent bug from 0.12 in nvim/lua/plugins/treesitter.lua: vim.treesitter.get_parser() now returns nil instead of throwing when there’s no parser, so if pcall(get_parser, buf) was truthy even for parser-less buffers and wrongly set indentexpr. Guarded on the returned parser. Confirmed the exact behavior headless (ok=true, parser=nil).
  • nvim-debug/functions/state.lua: vim.lsp.get_active_clients()get_clients() (deprecated since 0.10), plus a --[[@as string]] cast on the finddir/findfile result passed to fnamemodify.
  • Cleared a 171MB ~/.local/state/nvim/lsp.log that was tripping the size warning.
  • dap_repl parser: on nvim-treesitter’s main branch it isn’t a normal grammar — the nvim-dap-repl-highlights plugin registers it via a User TSUpdate autocmd, so the fix is a config fn running setup() then require('nvim-treesitter').install({'dap_repl'}). It wouldn’t compile inside the running nvim: tree-sitter build failed with error: tool 'clang' not found because that nvim was launched without the nix clang-wrapper on PATH (/usr/bin/clang → xcrun → no CLT). Compiled it via a headless nvim from a proper shell instead; the .so lands in the shared parser dir.
  • The “undefined global vim” mystery: two lua_ls instances with different roots. One roots at ~/.config and loads ~/.config/.luarc.json (clean — this is the instance the mcp-neovim-server tools drive). The other roots at ~/.config/nvim (pinned by nvim/.luacheckrc) and never reads the parent .luarc.json, so it flags vim. Crucially, the diagnostics Claude surfaces come from the claudecode.nvim instance (:51676), a different nvim than the MCP one — so vim.diagnostic.get() looked clean while Claude’s feed showed errors. Proven from ~/.cache/lua-language-server/log/file_*.log (rootUri + “Load config from .luarc.json” counts).

What was striking

  • The get_parser change is a nasty silent one — no error, no warning, just wrong indentation on files without a parser.
  • Two editors, two language servers, two roots: the “phantom” diagnostics weren’t phantom at all, just from a workspace root I wasn’t querying.
  • .luarc.json deployed to the XDG root only happened to work whenever lua_ls rooted at ~/.config; the .luacheckrc marker quietly created a second root where it didn’t.

Resolution

Fixed the lua_ls split-root in nix/home-manager/modules/neovim.nix: DRY let luarcJson = … deployed to both ~/.config/.luarc.json and ~/.config/nvim/.luarc.json (library paths are absolute, so it works at either root). hm switch + verified headless: an nvim rooted at ~/.config/nvim now reports 0 undefined-global. Shipped as four atomic commits (3b50bdb, 841fa7b, 93666f4, e0e1f0c), all pre-commit hooks green.