Skip to content
| Marketplace
Sign in
Visual Studio Code>Linters>Anvil — Protect Risky CallsNew to Visual Studio Code? Get it now.
Anvil — Protect Risky Calls

Anvil — Protect Risky Calls

anvilcontrol

|
1 install
| (0) | Free
Inline protection for the risky calls, agents, and workflows in your code. Wrap them where you write them, then watch and enforce from your Anvil control plane. Works in VS Code and Cursor.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Anvil — Protect Risky Calls (VS Code / Cursor)

Your code, and the AI agents you ship, take real actions in real systems: they move money, call APIs, write to your database, run workflows. Without limits, one wrong step becomes a real incident: a runaway bill, a deleted table, a leak you can't take back.

Anvil is the boundary those actions run through, and this extension brings it into your editor. Put your cursor on a risky call, an agent action, or a workflow, press Protect, and it's wrapped: safe to retry, wired to your control plane.

Every wrap starts in ghost mode: the real call runs, Anvil watches and records, nothing is blocked yet. Flip it to enforce from your dashboard when the proof is obvious, and the boundary holds: no double-charge, no overspend, no action it shouldn't take. Undo anytime with Cmd/Ctrl+Z.

No account to start: install and wrap a call — the wrap is real, and Cmd/Ctrl+Z takes it back. Watching it is the other half: a wrapped call isn't on a control plane until one is there, so nothing is observing or enforcing it yet. In an Anvil project the editor can start a plane on your machine for you (anvil.autoStartLocalPlane), and Anvil: Connect to your control plane points it at your hosted or self-hosted one whenever you're ready.

Five ways to protect a call

All produce the byte-identical wrap (same codemod as the Anvil CLI and MCP server):

Trigger When to use
CodeLens "Protect verb — why" above the call One click; appears automatically on recommended calls
Right-click → Protect with Anvil On the call your cursor is on (any call, not just recommended)
Cmd+Alt+A (Ctrl+Alt+A on Windows/Linux) Keyboard. Recommended/write calls wrap instantly; read-looking calls (db.query, getUser …) confirm first so you don't silently wrap a read
Cmd+Shift+P → "Anvil" Command palette
Type anvil (or protect) on a call line IntelliSense offers an Anvil completion that wraps that line's call

Lifecycle (the inline icon tells you the state)

State Inline icon Meaning
Unprotected grey anvil, on the lens Anvil suggests protecting this; nothing's wrapped yet
Ghost a ghost, before the call Wrapped, watching only: it records duplicates and blocks nothing. Lens above offers "Enforce verb?"
Enforce a blue anvil, before the call Live and enforcing the boundary. Lens above offers "Back to ghost?"
Off none — the call is unmarked Switched off in Anvil: not blocking, not deduping, not recording. Lens above offers "Anvil is off for verb — turn back on?"
Removed none — the call is unmarked Removed in Anvil. Any wrapper still in the file is inert (it observes in ghost and cannot block). Lens above offers "Anvil removed this — take the wrapper out?"

The blue anvil is the earned state: it appears only when Anvil is actually enforcing. The two unmarked states are deliberately quiet — a call Anvil is not acting on should not wear a badge that says it is.

Removing an action

Remove Anvil from this action (right-click, or the command palette) turns Anvil off for a call completely and takes the wrapper out of your file. It's distinct from Off, which pauses Anvil without touching your source. Your decision history and receipts are kept either way.

When someone removes an action in the Anvil dashboard, the leftover wrapper in your code is inert, and the editor takes it out for you — saved to disk, with a toast naming the file and offering Undo. It only ever touches a file this workspace actually wraps, found by the extension's own scan and never by a path the server supplied. If the file has unsaved changes it stops and says so, with Save and remove to finish it (pressing Cmd/Ctrl+S yourself works too); if it has uncommitted changes it asks before editing. An action wrapped in more than one file is removed one file at a time, and Anvil only reports it clear once no wrapper is left. Turn the whole behaviour off with anvil.autoRemoveWrapper if you'd rather remove wrappers by hand.

Undo puts both halves back — the wrapper in your file and the action on your control plane, in the mode it was in before (an enforcing action comes back enforcing). If your plane can't be reached it says so rather than claiming success. The button lives on a toast, so if you miss it, Anvil: Undo the last wrapper removal does the same thing from the command palette.

What it does on Apply

  1. Computes the wrap in memory and (for menu/lens) shows a side-by-side diff preview.
  2. Writes a single undoable edit (WorkspaceEdit).
  3. Picks a durable idempotency key from the call's first argument (orderId, paymentIntentId, userId, …). If none can be inferred, it inserts a // TODO(anvil) and lands your cursor on the key so you can type the real id.
  4. Folds the wrap boilerplate so the result is a clean, readable line.
  5. Offers to install @useanvil/sdk if it's not already a dependency.

Never auto-wraps. Never writes without confirmation (except the keyboard shortcut on already-recommended calls, which applies directly — Cmd+Z to undo).

Settings

  • anvil.enable — turn the lenses/actions on or off (default: on).
  • anvil.riskyActions — extra glob patterns to treat as risky, e.g. ["*.runTool", "queue.publish"] (added to the built-in money + destructive taxonomy).
  • anvil.includeComms — also flag email/SMS sends (off by default).
  • anvil.includeSideEffects — also flag generic side-effects: webhooks, queue publishes, database writes, external API mutations (webhook.publish, queue.enqueue, api.post, db.upsert). Off by default so Anvil flags only the highest-confidence calls (money, destructive) with no false positives. On, it stays precise: strong external verbs flag alone, ordinary mutating verbs flag only in an external-system context, and collections/logs/reads (array.push, map.set, logger.write) never flag.
  • anvil.autoRemoveWrapper — after you remove an action in Anvil, take its leftover wrapper out of your code automatically (default: on). This is the one setting that lets Anvil edit a file you didn't ask it to touch in that moment, so it is deliberately narrow: only an action this workspace wraps, never a file with unsaved or uncommitted changes, and always with Undo. Turn it off to remove wrappers by hand from the lens instead.
  • anvil.controlPlaneUrl — origin of your Anvil control plane. One origin serves both the dashboard UI (/app, opened by View in Anvil) and the API the editor talks to. Leave blank to use the hosted Anvil control plane (the default). Point it at your own deployment (e.g. https://your-app.up.railway.app) or http://localhost:7446 for a local control plane. Commit it to .vscode/settings.json so your whole team targets the same control plane.
    • Deprecated overrides (still honored for back-compat): anvil.dashboardUrl overrides just the View in Anvil page URL, and anvil.apiUrl overrides just the API base URL — useful for a split dev setup (e.g. a dashboard mockup on :8088 with the API on :7446). Prefer anvil.controlPlaneUrl; a value in either deprecated key wins over the derived origin.

Zero-config local plane

When anvil.controlPlaneUrl is blank and no token is set, the editor connects to a control plane on your machine on its own — install, wrap a call, watch it get tracked, nothing to run by hand. Three machine-scoped settings govern it:

  • anvil.autoConnectLocalPlane — detect a plane already running on localhost and read ghost/enforce from it over http://127.0.0.1, no token (control reads are open on loopback). On by default. Off keeps the editor on the optimistic ghost default until you connect. Only auto-detect is affected — an explicit localhost anvil.controlPlaneUrl is always honored.
  • anvil.autoStartLocalPlane — start one when none is running. In an Anvil project (the CLI is installed) with no plane on the port, the editor runs anvil serve in the background; it stays up as your local dashboard, single-instance, loopback only. On by default. Off keeps the editor from ever starting a server. Anvil: Start local control plane works either way.
  • anvil.localPlanePort — the port to find and start the local plane on (default 7446). Change only if you run yours elsewhere.

Control token (secure)

Pointing the editor at a control plane that requires auth — a self-hosted deployment behind ANVIL_CONTROL_TOKEN — takes one command: Anvil: Sign in / Set control token (Cmd/Ctrl+Shift+P → "Anvil"). The token is kept in the editor's secret storage, never in settings.json, and is handed to the anvil CLI as ANVIL_CONTROL_TOKEN for control-plane requests. Leave the box blank to keep the current token, or to remove it.

Without a token the editor stays local-only — it reads and writes your local control plane exactly as before. A status-bar item tells you which: a key when a token is configured, local only if you removed one. The token is never printed to logs.

For the hosted Anvil cloud there is nothing to configure by hand: run Anvil: Connect to your control plane, leave the URL blank, and paste either the install token your dashboard hands you (itk_…) or a project's existing ANVIL_API_KEY (anv_live_…). What gets stored is a per-workspace, revocable key — never a shared operator secret, and never anything in settings.json.

Connecting from your dashboard

Your Anvil dashboard can connect this editor for you: it opens a link your editor handles, the editor exchanges it for your workspace credential, and you get "connected to <your workspace>". Nothing to copy, nothing to paste.

Because a link like that can be opened by any page, the editor treats it as untrusted:

  • It will only ever talk to Anvil's own API, your anvil.controlPlaneUrl, or your anvil.apiUrl — and only over https (or http to your own machine). Any other address is refused, and nothing is sent.
  • It verifies the credential before storing it. The key is used once, against your control plane, and only a successful read stores it and moves the status bar. A key that is refused, revoked, or for a different workspace is discarded, and any credential you already had is left exactly as it was.
  • If this editor is already connected, it asks first, naming the plane it is currently on.
  • The link is single-use and short-lived. Nothing about it is ever written to the output channel.

Troubleshooting

No lenses on a file you expected to be flagged? Open View → Output → "Anvil". The channel logs slow parses (>250 ms), skipped large files (>250 KB), and codemod errors with the file path so you can see why.

The keyboard shortcut opens a search bar? Cursor binds many Cmd+Shift combos to its own search. The default is Cmd+Alt+A; rebind via Cmd+K, Cmd+S → search "Anvil" → press your preferred keys.

Ghost/enforce toggle says it couldn't reach your control plane? Which path the flip takes depends on where your control plane is. Against the hosted Anvil cloud (the default, anvil.controlPlaneUrl blank) it goes over HTTP with your workspace key, so the fix is usually Anvil: Connect to your control plane. Against a local or self-hosted plane it goes through the anvil CLI, which needs that plane's store reachable from your machine. Anvil: Refresh control state from Anvil reports which plane the editor is actually talking to, which is the fastest way to tell the two apart.

Install

From the marketplace (recommended). Search "Anvil — Protect Risky Calls" in the Extensions view, or from a terminal:

code   --install-extension anvilcontrol.anvil-protect   # VS Code
cursor --install-extension anvilcontrol.anvil-protect   # Cursor / VSCodium (via Open VSX)

VS Code installs from the Visual Studio Marketplace. VSCodium pulls the same build from Open VSX, and Cursor resolves it through its own gallery — either way it's the identical artifact.

From a .vsix (offline / pre-release). Build with npm run package, then Extensions → … → Install from VSIX. Cursor runs VS Code extensions unmodified, so one build covers both.

Maintainers: RELEASE_CHECKLIST.md documents how releases are published.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft