Anvil v2.2.18: Fix the Compaction Bug, Restore Copy-Paste, Finish the Web Viewer
Three bugs. One release. Anvil v2.2.18 lands today with fixes that have been quietly annoying users for several versions, plus the web viewer finally earning its tab-parity badge with the desktop TUI.
The autocompact bug you didn’t know you had
If you’ve been using Anvil on claude-sonnet-4-5, Gemini 1.5 Pro, or any other long-context model and noticing that sessions feel like they compact after just a few turns — you weren’t imagining it. There was a real bug.
The culprit: maybe_auto_compact was computing its 80% threshold against max_output_tokens rather than the actual context window size. max_output_tokens is typically 8,000 to 16,000 tokens. Your context window is 64,000 to 200,000+ tokens. The result: on a model with a 200K context window, Anvil was firing autocompact when the session reached roughly 6,400 tokens of input — about 3% of the available window.
In practice this meant most long-context model sessions were being compacted after 3–5 turns rather than 30–50 turns. You’d see the “session compacted” notice, lose some context, and carry on — never knowing the session had given up 97% of its available runway.
The fix is one-line: threshold computation now reads session.context_window from the provider’s /models response. The 80% trigger percentage is unchanged. If you configure a max_context_tokens override in your settings, that value is used instead. You don’t need to change anything — the fix is automatic on upgrade.
To verify it’s working after upgrading, run /compact why. Anvil will print the threshold calculation including which window size it’s using. A correctly configured claude-sonnet-4-5 session should show a threshold around 160,000 tokens, not 6,400.
Mouse capture: back to the safe default
Mouse capture in the terminal is one of those features where defaulting ON has a real cost: it silently breaks copy-paste in most terminals. On macOS Terminal.app, copy is Cmd+C. On Gnome Terminal and kitty, it’s Ctrl+Shift+C. On Windows Terminal, it’s Ctrl+C. When mouse capture is active, the terminal hands those events to the application instead of handling them as keyboard shortcuts — and if the application isn’t expecting them, nothing happens.
Anvil’s mouse capture mode (which enables scroll and click support inside the TUI) was defaulting ON. That meant every new user on Gnome Terminal, kitty, or Windows Terminal who installed Anvil and tried to copy a code snippet from the output found that Ctrl+Shift+C did nothing. Not ideal for a first impression.
v2.2.18 flips the default to OFF. Mouse capture is now explicitly opt-in: /config mouse_capture true or the --mouse flag at launch. A one-time toast explains the tradeoff when you first run. Users who had mouse capture working before — and who knew what they were doing — can re-enable it in one command.
A type-level regression test (mouse_capture_default_off_regression) asserts TuiConfig::default().mouse_capture == false so this default can’t silently change in a future diff without breaking a test.
Web viewer: tabs actually work now
The AnvilHub web viewer has had a tab UI since v2.2.16 introduced the tab architecture in the desktop TUI. What it didn’t have was tab routing. Every session’s messages broadcast to every connected viewer regardless of which tab created them. If you had two active sessions, messages from session A would appear in session B’s scrollback.
The root cause was a paired_count gate in the relay that was supposed to prevent duplicate events on multi-viewer setups. Instead, it blocked correct routing after the first reconnect. Removing the gate and replacing it with stable tab IDs (generated once at creation, never reused) fixed the routing entirely.
v2.2.18 also lands the full /tab command set in the viewer: /tab new, /tab rename <name>, /tab switch <n>, and Ctrl+T for new-tab from any state. The viewer’s default layout is now Vertical Split + Tabs, matching the TUI default.
The relay carries more information in this release too. The status footer now shows a proper cost-type chip (OAuth, local, or cloud) instead of a fabricated dollar amount for providers where per-token cost isn’t publicly available. Memory snapshots are cached and broadcast so the memory rail in the viewer populates correctly after a reconnect. Session metadata includes context_max so the context-window progress bar reflects the actual window size — not a hardcoded 100K.
TUI stability: the keyboard-dies bug
One v2.2.17 regression got widespread enough to warrant a callout: after canceling an /mcp builder flow or any other inline operation that temporarily leaves the alt-screen, the TUI keyboard would stop responding. Characters typed after that point never reached the input box.
The bug was in restore_alt_screen: it was restoring the alt-screen buffer but not re-enabling raw mode. Crossterm’s raw mode is what routes keyboard input to the application instead of to the shell. Once it was off, input processing effectively stopped. The fix is one line: terminal::enable_raw_mode()? re-added to the restore path.
Wizard paste fix (#685)
Bracketed paste — the terminal protocol that wraps clipboard content in x1b[200~ / x1b[201~ markers so applications can distinguish pasted text from typed input — now works inside textarea modals. This affects the multi-line description field in /mcp builder, the long-prompt field in several wizard steps, and any other TextareaModal in the UI.
The fix wires the existing tui::paste::handle_paste logic (the same code that handles paste in the main input box) into the textarea modal event loop. Line-ending normalization (rn → n) and bracketed-paste sequence stripping apply identically.
Release pipeline hardening (#654)
release.sh Phase 6 runs SSH-based deploy steps against production. With set -e active at the script level, a failed SSH hop should immediately abort the pipeline. It wasn’t: certain SSH invocations in Phase 6 were constructed in ways that allowed their exit codes to be masked, letting the script proceed to subsequent phases against a stale remote state.
Every Phase 6 SSH call now has an explicit || { echo "Phase 6 SSH failed: ..."; exit 1; } guard. Failed deploys surface immediately rather than silently corrupting the release state.
Upgrade path
v2.2.18 is binary-compatible with v2.2.17 sessions. anvil --continue and anvil --resume <id> work across the upgrade without data migration.
The two new config keys (mouse_capture, mouse_capture_toast_seen) are optional — existing ~/.anvil/config.json files parse without changes. The autocompact fix is fully automatic; no configuration change is needed or recommended.
# macOS / Linux:
curl -fsSL https://anvilhub.culpur.net/install.sh | sh
# Homebrew:
brew upgrade culpur/anvil/anvil
# Windows / FreeBSD / NetBSD:
# https://github.com/culpur/anvil/releases/tag/v2.2.18
Full release notes: github.com/culpur/anvil/releases/tag/v2.2.18
