Skip to content

fix(autocmds): guard debounced cursor action against wiped buffer#519

Merged
OXY2DEV merged 1 commit into
OXY2DEV:mainfrom
ImmanuelHaffner:fix/cursor-action-buffer-validity
Jul 6, 2026
Merged

fix(autocmds): guard debounced cursor action against wiped buffer#519
OXY2DEV merged 1 commit into
OXY2DEV:mainfrom
ImmanuelHaffner:fix/cursor-action-buffer-validity

Conversation

@ImmanuelHaffner

Copy link
Copy Markdown
Contributor

Problem

Moving the cursor in a Markdown/preview buffer can crash with:

Invalid buffer id: N
stack traceback:
	[C]: in function 'nvim_buf_clear_namespace'
	.../markview/renderers/markdown.lua: in function 'clear'
	.../markview/renderer.lua: in function 'clear'
	.../markview/actions.lua: in function 'clear'
	.../markview/actions.lua: in function 'render'
	.../markview/autocmds.lua: in function 'fn'

Root cause

The cursor autocmd (CursorMoved / CursorMovedI) in autocmds.lua defers its work through a debounce timer wrapped in vim.schedule_wrap:

autocmds.cursor_timer:start(delay, 0, vim.schedule_wrap(action));

So the action closure runs on a later event-loop tick than the autocmd that queued it. The buffer id captured in args.buf is validated when the autocmd fires (state.buf_attached(args.buf) etc.), but not when the scheduled callback finally runs.

If that buffer is wiped during the debounce window — e.g. a transient Telescope preview buffer, a scratch buffer, or any :bwipeout — the captured id becomes dangling. The callback then flows through actions.render/actions.clearrenderer.clearmarkdown.clearnvim_buf_clear_namespace with a dead id, and none of those layers re-validate the buffer, so the raw API call throws.

Fix

Re-validate args.buf at the top of the scheduled action closure, so it bails out cleanly when the buffer no longer exists:

local function action ()
	if not args.buf or not vim.api.nvim_buf_is_valid(args.buf) then
		return;
	end
	--
end

This is the tightest single choke point — it protects the splitview_render, render, and clear paths at once, right before any nvim_buf_* call can reach the stale id.

Notes

  • Behaviour is unchanged for live buffers; only the wiped-buffer race is short-circuited.
  • Reproducible reliably by opening a Telescope preview of a Markdown file and moving the cursor as the preview buffer is recycled/wiped.

The cursor autocmd (CursorMoved/CursorMovedI) defers its work through a
debounce timer wrapped in vim.schedule_wrap, so the `action` closure runs on
a later event-loop tick than the autocmd that queued it. The buffer id
captured in `args.buf` is validated when the autocmd fires but not when the
scheduled callback finally runs.

If that buffer is wiped during the debounce window (e.g. a transient Telescope
preview or scratch buffer), the captured id becomes dangling. The callback
then reaches actions.render/clear -> renderer.clear -> markdown.clear ->
nvim_buf_clear_namespace with a dead id, throwing:

    Invalid buffer id: N

Re-validate `args.buf` at the top of the scheduled closure so it bails out
cleanly when the buffer no longer exists. Behaviour is unchanged for live
buffers; only the wiped-buffer race is short-circuited.
@OXY2DEV OXY2DEV merged commit 092c087 into OXY2DEV:main Jul 6, 2026
1 check passed
@ImmanuelHaffner ImmanuelHaffner deleted the fix/cursor-action-buffer-validity branch July 7, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants