Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/fredrikaverpil-nvim-install/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-13 | ✗→✓ | ▲ Improved | 98% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 119% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 119% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 77% | 0% |
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 is managed by bob at ~/.local/share/bob/nvim-bin/nvim.
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. bob is available too (see the end), but bob install downloads Neovim as a GitHub release asset, which the GitHub proxy blocks unless neovim/neovim is attached to the session — regardless of network level. So Nix is the default; bob is an opt-in.
Two independent mechanisms gate traffic; they bite at different points.
*.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
nvim-fredrik run): at startup vim.pack clones ~50plugins. These split three ways:
(github.com is allow-listed). No action.
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.)
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.
Follow the nix-install skill (apt nix-bin + single-user config). In short:
bashapt-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
Everything is substituted from cache.nixos.org; nothing is compiled and no GitHub is touched.
bashnix-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
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.
bashrepo="/home/user/dotfiles" # adjust if the repo is cloned elsewhere mkdir -p ~/.config ln -sfn "$repo/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
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.
bashsource ~/.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.
neovim skillbashsource ~/.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.
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.
bashsource ~/.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:
bashsource ~/.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.
The nvim-fredrik config is part of this repo, so a PR that changes it is just a branch checkout — no separate config repo:
bashgit -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:
pkill -f 'nvim --headless --listen'. Each Bash call runs asbash -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.
$NVIM. A dying nvim unlinks itsown 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.
bashsource ~/.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:
bashnvim --server "$NVIM" --remote-expr 'luaeval("vim.json.encode(vim.diagnostic.get(0))")'
If you specifically want a bob-managed version (one-word stable/nightly switch, matching the real machines), install bob from Nix and let it fetch Neovim — but note bob's download is a GitHub release asset, so neovim/neovim must be attached to the session first (add_repo neovim/neovim; Claude only runs it when you ask). Network level alone will not unblock it.
bashTARBALL=https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz nix-env -iA bob-nvim -f "$TARBALL" # the binary is `bob` export PATH="$HOME/.nix-profile/bin:$PATH" bob use nightly # needs neovim/neovim attached export PATH="$HOME/.local/share/bob/nvim-bin:$PATH"
If you use this, add $HOME/.local/share/bob/nvim-bin to the PATH line in ~/.nvim-sandbox.env (Step 3). For most sandbox testing the Step 2 Nix binary is simpler and needs no attachment.
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.
vim.diagnostic.get,vim.lsp.get_clients, buffer APIs), not by looking at a screen.
lifetime; nothing is committed. Re-run the skill in a fresh environment.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-13 | fail→pass | 14,175 | 6,887 | -51% | 1 | 1 | 0% | 2,409 | 4,759 | +98% | 0 | 0 | — |
case-07 | pass→pass | 9,597 | 2,150 | -78% | 1 | 1 | 0% | 1,587 | 3,873 | +144% | 0 | 0 | — |
case-01 | fail→fail | 20,398 | 35,910 | +76% | 1 | 1 | 0% | 3,952 | 3,865 | -2% | 0 | 0 | — |
case-02 | fail→fail | 6,660 | 6,261 | -6% | 1 | 1 | 0% | 1,047 | 3,941 | +276% | 0 | 0 | — |
case-03 | fail→fail | 9,553 | 8,067 | -16% | 1 | 1 | 0% | 1,673 | 3,975 | +138% | 0 | 0 | — |
case-04 | pass→pass | 14,081 | 6,971 | -50% | 1 | 1 | 0% | 2,450 | 4,724 | +93% | 0 | 0 | — |
case-05 | pass→pass | 12,319 | 10,626 | -14% | 1 | 1 | 0% | 2,483 | 4,939 | +99% | 0 | 0 | — |
case-06 | pass→pass | 7,064 | 9,516 | +35% | 1 | 1 | 0% | 1,198 | 5,158 | +331% | 0 | 0 | — |
case-08 | fail→pass | 11,949 | 5,966 | -50% | 1 | 1 | 0% | 2,115 | 4,640 | +119% | 0 | 0 | — |
case-09 | fail→pass | 11,399 | 5,141 | -55% | 1 | 1 | 0% | 2,049 | 4,486 | +119% | 0 | 0 | — |
case-10 | pass→pass | 5,446 | 2,413 | -56% | 1 | 1 | 0% | 871 | 3,984 | +357% | 0 | 0 | — |
case-11 | pass→pass | 6,108 | 2,724 | -55% | 1 | 1 | 0% | 1,208 | 4,004 | +231% | 0 | 0 | — |
case-12 | pass→pass | 13,515 | 7,899 | -42% | 1 | 1 | 0% | 2,338 | 5,008 | +114% | 0 | 0 | — |
case-14 | pass→pass | 5,519 | 2,947 | -47% | 1 | 1 | 0% | 924 | 4,071 | +341% | 0 | 0 | — |
case-15 | fail→fail | 15,448 | 6,641 | -57% | 1 | 1 | 0% | 2,664 | 4,711 | +77% | 0 | 0 | — |
case-16 | pass→pass | 7,315 | 2,274 | -69% | 1 | 1 | 0% | 1,128 | 3,900 | +246% | 0 | 0 | — |
case-17 | fail→pass | 14,338 | 5,488 | -62% | 1 | 1 | 0% | 2,395 | 4,371 | +83% | 0 | 0 | — |
case-18 | pass→pass | 9,155 | 2,396 | -74% | 1 | 1 | 0% | 1,433 | 3,900 | +172% | 0 | 0 | — |
case-19 | pass→pass | 7,458 | 2,150 | -71% | 1 | 1 | 0% | 1,308 | 3,872 | +196% | 0 | 0 | — |
case-20 | pass→pass | 6,909 | 2,958 | -57% | 1 | 1 | 0% | 1,270 | 3,996 | +215% | 0 | 0 | — |
case-21 | fail→pass | 13,719 | 5,445 | -60% | 1 | 1 | 0% | 2,532 | 4,474 | +77% | 0 | 0 | — |
case-22 | fail→pass | 6,758 | 1,198 | -82% | 1 | 1 | 0% | 1,182 | 3,702 | +213% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +27 percentage points is the difference between those two pass rates over the 19 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.