---
name: lua-helper
description: Current Lua guidance for portable Lua, LuaJIT, Neovim's Lua interface, WezTerm configuration, LuaLS, formatting, linting, testing, and security. Use when writing or reviewing Lua, Neovim plugins/config, WezTerm config, rockspecs, or Lua tooling.
---

# Lua Helper

Identify the host before writing Lua. Portable Lua 5.5, Neovim's Lua 5.1 interface, optional LuaJIT extensions, and WezTerm's Lua 5.4 runtime have different APIs and semantics.

## Current baselines

Verified 2026-08-03:

| Host or tool | Current baseline | Compatibility boundary |
| --- | --- | --- |
| Lua | 5.5.0; maintained 5.4 line at 5.4.8 | Version-gate APIs newer than the project's minimum |
| LuaJIT | Active rolling 2.1 branch | Lua 5.1-compatible with implementation extensions and build flags |
| Neovim | 0.12.4 | Permanent Lua 5.1 interface; may use LuaJIT or a compatible fork |
| WezTerm | Stable release still dated 2024 | Embeds Lua 5.4; online docs can describe nightly-only APIs |
| LuaLS | 3.18.2 | Annotations and diagnostics are tooling, not runtime validation |
| StyLua | 2.5.2 | Pin/configure per repository |
| LuaRocks | 3.13.0 | Installation runs package build logic and mutates environments |

Read [references/releases.md](references/releases.md) for the 68-page research ledger. Read [references/core-language.md](references/core-language.md) for portable Lua and LuaJIT differences. Read [references/neovim.md](references/neovim.md) for current LSP, diagnostics, process, trust, and buffer APIs. Read [references/wezterm.md](references/wezterm.md) for configuration evaluation, strict mode, events, subprocesses, and mux domains. Read [references/tooling-and-security.md](references/tooling-and-security.md) for LuaLS, formatters, linters, tests, rocks, and security.

## Select the runtime

Before using an API, establish:

1. Lua language/interface version.
2. Whether the runtime is PUC Lua, LuaJIT, or a host embedding.
3. Host application version and stable/nightly channel.
4. Tooling version and project configuration.

In Neovim, target the documented Lua 5.1 interface and check `jit` before LuaJIT-specific behavior. In WezTerm, target Lua 5.4 and check the API's “Since” version against the installed binary.

## Error discipline

Lua APIs commonly return `nil, error` or status tuples. Check every open, read, write, close, subprocess, and host callback result whose failure matters.

```lua
local function read_file(path)
  local file, open_error = io.open(path, "rb")
  if not file then
    return nil, open_error
  end

  local content, read_error = file:read("*a")
  local close_ok, close_error = file:close()
  if not content then
    return nil, read_error
  end
  if not close_ok then
    return nil, close_error
  end
  return content
end
```

Do not bind an unchecked `io.open` result as a to-be-closed value. Preserve close failures when durability matters.

## Tables and sequences

The length operator does not count arbitrary table keys. For a table with holes, `#table` returns a valid border and is not a reliable element count. Model sequences as contiguous integer keys or count explicitly.

Version-gate `table.move`, `table.unpack`, `rawlen`, `__pairs`, and related APIs. Lua 5.1 uses global `unpack`; LuaJIT compatibility libraries can depend on build flags.

Avoid memoization keys built from `table.concat({...})`: they reject many value types and can collide. Use nested tables keyed by arguments or a deliberately encoded, restricted input domain.

## Neovim

Current native LSP setup uses `vim.lsp.config` and `vim.lsp.enable`. This replaces nvim-lspconfig's deprecated legacy `require('lspconfig').setup` framework; nvim-lspconfig itself remains maintained and supplies server definitions.

Use current APIs:

- `vim.hl.hl_op()` for yank highlighting.
- `vim.diagnostic.jump({ count = 1 })` and negative count for navigation.
- `vim.api.nvim_set_option_value(name, value, { buf = buffer })` for scoped options.
- `vim.system({ program, argument }, { text = true }):wait()` for ordinary subprocesses, checking `code`, `signal`, and `stderr`.

Use `:trust` and `vim.secure` for project-local configuration. `exrc` executes project code and is a trust boundary.

## WezTerm

Create the configuration with strict mode so invalid options fail:

```lua
local wezterm = require("wezterm")
local config = wezterm.config_builder()
config:set_strict_mode(true)

return config
```

WezTerm may evaluate configuration repeatedly. Keep top-level evaluation idempotent and free of side effects such as spawning processes. Config precedence includes CLI and environment overrides before standard paths.

`wezterm.run_child_process` returns success, stdout, and stderr. Inspect success. Returning `false` from a `wezterm.on` callback stops later callbacks and the default action.

## Tooling

```bash
stylua --check .
luacheck .
selene .
busted
```

Use the tools configured by the project. Choose Luacheck or Selene deliberately instead of layering both without purpose. Use Busted for behavior-style suites or LuaUnit for lightweight xUnit tests. Use headless Neovim/Plenary only when host integration is part of the contract.

LuaLS supports `.luarc.json` / `.luarc.jsonc`, annotations, diagnostics, addons, and stricter type-checking modes. Loading the entire Neovim runtime as a workspace library is valid but broad; prefer an addon or narrow explicit paths when sufficient.

## Security

- Load untrusted source only as text with a constrained environment; never accept untrusted binary chunks.
- Avoid shell construction in `os.execute` and string-form `vim.fn.system`. Never concatenate untrusted input into a shell command.
- WezTerm and Neovim config execute arbitrary code. Keep secrets out of config and use protected identity/agent mechanisms.
- WezTerm config can execute more than once; repeated evaluation magnifies side effects.
- LuaRocks installation executes rock build/package logic. Inspect sources and rockspecs and pin versions in reproducible environments.
- LuaJIT FFI is an implementation extension with native-memory safety implications; do not assume it exists or is sandboxed.

## Review checklist

- Identify portable Lua, LuaJIT, Neovim, or WezTerm before choosing APIs.
- Version-gate language, host, and online-documentation features.
- Check file, process, callback, and cleanup results.
- Do not use `#` as a general map size or unsafe concatenated memo keys.
- Use current Neovim LSP, highlight, diagnostic, option, and process APIs.
- Make WezTerm config strict, idempotent, and side-effect free at top level.
- Configure LuaLS and one intentional lint/test workflow.
- Treat project config, shells, rocks, binary chunks, and FFI as trust boundaries.
- Preserve the distinction between Neovim's Lua 5.1 interface and optional LuaJIT implementation.
