Skip to content
Back to skills

Nvim Install

BSecurity

Install Neovim and stand up the nvim-fredrik config in the Claude Code web sandbox (the cloud environment), where no Neovim exists. Use this before testing any Neovim config change in a web session, e.g. checking out a PR branch that touches nvim-fredrik and observing its runtime behavior. Installs Neovim via Nix from the binary cache with no GitHub access, wires the repo's config into place, launches Neovim headless with a listen socket, and exports $NVIM so the `neovim` RPC skill can drive ...

  • 260 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 4, 2026
toolspythonrustgoshellbashtestinggitapi

Works with

  • claude code
  • terminal
  • cli
  • api

Security analysis

B75/100
  • criticalModifies startup scripts or system services for persistence

Pro shows the line behind each finding and how to fix it

Scanned September 30, 2026

npx -y skills add fredrikaverpil/dotfiles --skill nvim-install --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Nvim Install?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Nvim Install
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/fredrikaverpil-nvim-install/badge)](https://www.skillsdirectory.com/skills/fredrikaverpil-nvim-install)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: nvim-install
description: Install Neovim and stand up the nvim-fredrik config in the Claude Code web sandbox (the cloud environment), where no Neovim exists. Use this before testing any Neovim config change in a web session, e.g. checking out a PR branch that touches nvim-fredrik and observing its runtime behavior. Installs Neovim via Nix from the binary cache with no GitHub access, wires the repo's config into place, launches Neovim headless with a listen socket, and exports $NVIM so the `neovim` RPC skill can drive it. Web-sandbox only.
---

# Install Neovim in the web sandbox

This is the missing half of the `neovim` RPC skill: that skill drives a running
Neovim via `$NVIM`, but the sandbox ships neither the binary nor a parent
editor. This skill installs Neovim, launches it **headless** on a listen socket,
and exports `$NVIM` so the `neovim` skill's commands work verbatim afterwards.

**Scope:** web sandbox only (`CLAUDE_CODE_REMOTE=true`). Do not run on a real
machine — there Neovim comes from nixpkgs-unstable via home-manager.

## Why Nix

Nix installs Neovim from `*.nixos.org` (allow-listed by default), so the binary
needs **no GitHub access** — and `nixpkgs-unstable` ships a current Neovim with
`vim.pack`, which `nvim-fredrik` requires. Same source as the real machines.

## Step 0 — Network prerequisites

Two independent mechanisms gate traffic; they bite at different points.

1. **The binary (this skill):** substituted from the Nix cache (`*.nixos.org`),
   reachable under the default **Trusted** network policy. No GitHub, no action.
   Verify:

   ```bash
   curl -sSI -o /dev/null -w "cache:    %{http_code}\n" https://cache.nixos.org
   curl -sSI -o /dev/null -w "channels: %{http_code}\n" https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz
   ```

2. **The plugins (a full `nvim-fredrik` run):** at startup `vim.pack` clones ~50
   plugins. These split three ways:
   - **Public GitHub clones** — already work under the default Trusted policy
     (`github.com` is allow-listed). No action.
   - **codeberg.org plugins** (nvim-lint, nvim-dap, nvim-dap-python) —
     **required and blocked by default.** Edit the environment → **Network
     access** selector → **Custom** → in **Allowed domains** add the bare
     `codeberg.org` → tick *"Also include default list of common package
     managers"* → save. This rebuilds the environment. See
     https://code.claude.com/docs/en/claude-code-on-the-web. **Gotcha:** use the
     apex `codeberg.org`, not `*.codeberg.org` — the wildcard matches only
     subdomains, but the plugin `src` URLs are `https://codeberg.org/...`, so a
     `*.codeberg.org`-only allowlist still 403s the clones. (`Full` network
     access also works but is broader than needed.)
   - **GitHub release assets** (codediff's prebuilt lib, Mason LSP servers,
     treesitter parsers) — gated by the GitHub proxy to *attached* repos
     **regardless of network level**, so they may 403. Optional for observing
     most config behavior; attach a specific repo with `add_repo` only if the
     change under test needs it.

## Step 1 — Ensure Nix is installed

Follow the **`nix-install`** skill (apt `nix-bin` + single-user config). In
short:

```bash
apt-get update
apt-get install -y --no-install-recommends nix-bin
mkdir -p /etc/nix
grep -q '^build-users-group' /etc/nix/nix.conf 2>/dev/null \
  || echo 'build-users-group =' >> /etc/nix/nix.conf
```

## Step 2 — Install Neovim from nixpkgs-unstable

Everything is substituted from `cache.nixos.org`; nothing is compiled and no
GitHub is touched.

```bash
nix-env -iA neovim -f https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz
export PATH="$HOME/.nix-profile/bin:$PATH"
nvim --version | head -1                                              # NVIM v0.12.x
nvim --headless -c 'lua io.write(tostring(vim.pack ~= nil))' -c 'qa'  # expect: true
```

## Step 3 — Wire the config into place and persist the environment

The config lives in the cloned repo. Two env vars point Neovim at it:

- **`NVIM_APPNAME=nvim-fredrik`** makes Neovim read `~/.config/nvim-fredrik`
  (the symlink created below).
- **`$DOTFILES`** is the **dotfiles repo root**. The config builds paths from it
  (Mason lockfile, lint configs, snippets); left unset it falls back to
  `~/.dotfiles`, which doesn't exist in the sandbox, so those lookups silently
  resolve to missing files. Point it at the clone root.

**Each Bash call is a fresh shell whose environment is snapshotted at session
start** — `~/.bashrc` is not re-run per call (its non-interactive `return` guard
exits early), so appending an `export` or `source` line there does **not** reach
later calls. The filesystem does persist, though: write the env to a file and
`source` it at the top of every later call that needs it — including every
`neovim`-skill call, which keys off `$NVIM`.

```bash
repo="/home/user/dotfiles"        # adjust if the repo is cloned elsewhere
mkdir -p ~/.config
ln -sfn "$repo/stow/shared/.config/nvim-fredrik" ~/.config/nvim-fredrik

cat > ~/.nvim-sandbox.env <<EOF
export PATH="\$HOME/.nix-profile/bin:\$PATH"
export DOTFILES="$repo"
export NVIM_APPNAME=nvim-fredrik
export NVIM=/tmp/nvim-fredrik.sock
EOF
source ~/.nvim-sandbox.env
```

## Step 4 — Launch Neovim headless with a listen socket

This is the bridge to the `neovim` skill. Start a backgrounded headless
instance listening on `$NVIM`, so the RPC skill's commands — which all key off
`$NVIM` — work unchanged. The **first** launch clones all plugins via `vim.pack`
(needs Step 0 item 2) and can take a few minutes.

```bash
source ~/.nvim-sandbox.env        # env doesn't persist across calls; re-source
rm -f "$NVIM"
# --headless keeps it alive as long as the socket is served; run detached.
nohup nvim --headless --listen "$NVIM" >/tmp/nvim-headless.log 2>&1 &
echo $! > /tmp/nvim-fredrik.pid   # record PID so a later restart can kill it
# Wait for the socket to accept RPC (plugin cloning happens during startup).
for i in $(seq 1 120); do
  [ -S "$NVIM" ] && nvim --server "$NVIM" --remote-expr '1' >/dev/null 2>&1 && break
  sleep 2
done
echo "NVIM socket: $NVIM"; tail -8 /tmp/nvim-headless.log
```

`403` / `CONNECT tunnel failed` lines in the log point at the Step 0 item 2
gaps (usually `codeberg.org` if it isn't allowed yet, or a GitHub release
asset). The editor still runs; only the plugins that failed to clone are
missing.

## Step 5 — Verify and hand off to the `neovim` skill

```bash
source ~/.nvim-sandbox.env
nvim --server "$NVIM" --remote-expr 'v:version'
nvim --server "$NVIM" --remote-expr 'luaeval("vim.fn.stdpath(\"config\")")'
```

With `~/.nvim-sandbox.env` sourced, `$NVIM` is set exactly as if Claude Code
were running inside a Neovim terminal, so **switch to the `neovim` skill** for
all further interaction (buffer state, running Lua, inspecting diagnostics, LSP,
plugins). Start each of those Bash calls with `source ~/.nvim-sandbox.env` too —
the env doesn't carry over on its own.

## One-shot checks (skip the persistent server)

Steps 4–5 stand up a *persistent* `--listen` server so the `neovim` skill can
drive it across turns. You only need that for interactive, multi-step work. For
an observe-once question — *does this config change load cleanly? what does the
formatter do to this file?* — skip the socket entirely and use a
**self-terminating** headless run that loads the real config, does its thing,
prints, and quits with `qa!`. It exits cleanly and sidesteps all of the
launch/restart/socket handling (and its footguns) above. Prefer it whenever you
don't actually need cross-turn RPC.

```bash
source ~/.nvim-sandbox.env
# "does my config change load without error?"
nvim --headless \
  -c 'lua io.write("config="..vim.fn.stdpath("config").."\n")' \
  -c 'qa!'
```

Exercising behaviour follows the same shape — open a file so its filetype
plugins load, run the operation, print, quit:

```bash
source ~/.nvim-sandbox.env
nvim --headless path/to/file.go \
  -c '<command that performs the operation, e.g. runs the formatter>' \
  -c 'lua io.write(table.concat(vim.fn.getline(1, "$"), "\n").."\n")' \
  -c 'qa!'
```

`-c` commands run in order after startup. Opening a file fires `FileType`
loading, but a plugin that lazy-loads on its *own* event or command only
activates once you invoke that command — so trigger the real operation rather
than assuming the plugin is already loaded.

## Testing a config-change PR

The `nvim-fredrik` config is part of *this* repo, so a PR that changes it is
just a branch checkout — no separate config repo:

```bash
git -C /home/user/dotfiles fetch origin <pr-branch>
git -C /home/user/dotfiles checkout <pr-branch>
```

Then restart the headless instance so the new config is sourced. Kill the old
instance by the PID recorded at launch and **wait until it is actually gone**
before relaunching. Two traps make this fiddlier than it looks:

- **Don't `pkill -f 'nvim --headless --listen'`.** Each Bash call runs as
  `bash -c '<the whole script>'`, so that string is in the script shell's own
  arguments; `pkill -f` matches and kills the script itself, which dies with a
  `128 + signal` exit (e.g. `143`/`144`) before it ever relaunches. Killing the
  recorded PID sidesteps the pattern entirely.
- **Let the old process exit before reusing `$NVIM`.** A dying nvim unlinks its
  own socket on the way out. Relaunch on the same path too soon and the old
  instance's cleanup deletes the *new* socket — RPC then fails with "connection
  refused" even though the new process is alive. So confirm it's gone (SIGKILL
  as a fallback) before `rm`-ing the path and starting fresh.

```bash
source ~/.nvim-sandbox.env
oldpid="$(cat /tmp/nvim-fredrik.pid 2>/dev/null)"
if [ -n "$oldpid" ]; then
  kill "$oldpid" 2>/dev/null || true
  for i in $(seq 1 50); do kill -0 "$oldpid" 2>/dev/null || break; sleep 0.2; done
  kill -9 "$oldpid" 2>/dev/null || true   # force if it ignored SIGTERM
fi
rm -f "$NVIM"
nohup nvim --headless --listen "$NVIM" >/tmp/nvim-headless.log 2>&1 &
echo $! > /tmp/nvim-fredrik.pid
for i in $(seq 1 120); do
  [ -S "$NVIM" ] && nvim --server "$NVIM" --remote-expr '1' >/dev/null 2>&1 && break
  sleep 2
done
```

Now drive the behavior under test via the `neovim` skill — e.g. for a
neotest/diagnostics change, open a test file, run the command, then read the
diagnostics:

```bash
nvim --server "$NVIM" --remote-expr 'luaeval("vim.json.encode(vim.diagnostic.get(0))")'
```

## Caveats

- **First launch is slow** — plugin cloning is network-bound and serial.
- **Mason / LSP servers and treesitter parsers are best-effort.** They pull
  extra tools from GitHub release assets and compile locally; if a change under
  test doesn't depend on a language server, you don't need them. Failures are
  logged in `/tmp/nvim-headless.log` and don't prevent the rest of the config
  loading.
- **Headless has no UI.** Inspect state via RPC (`vim.diagnostic.get`,
  `vim.lsp.get_clients`, buffer APIs), not by looking at a screen.
- **Ephemeral.** The install and config live only for this environment's
  lifetime; nothing is committed. Re-run the skill in a fresh environment.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.