Skip to main content

spiceguard

ngspice said exit 0. The answer was still wrong.

Modern ngspice recovers from many classic convergence problems on its own — and when it can't, it often returns exit code 0 with a plausible but wrong answer (a relaxed fallback estimate, or arbitrary voltages on an ungrounded node). Nothing in the standard flow warns you. This bites hardest on netlists you didn't write yourself: AI-generated circuits and PR submissions.

spiceguard answers one question about a SPICE run: can I trust this result? It combines static netlist analysis, ngspice failure-log decoding, and silent-failure detection — as a CLI, an MCP server for AI agents, a VS Code extension, and a KiCad workflow.

Project history (image-to-SPICE origins and the pivot): docs/HISTORY.md.


Install

Requirements: Python 3.9+, ngspice

brew install ngspice          # macOS; Linux: apt install ngspice

From PyPI:

pip install spiceguard

From the repo (development):

pip install .
# or, without installing:
PYTHONPATH=src python3 -m spiceguard FILE...

Docker (zero setup — ngspice bundled):

docker build -t spiceguard -f docker/Dockerfile .
docker run --rm -v "$PWD:/work" spiceguard mycircuit.cir

This registers a spiceguard command on your PATH.


ngspice path resolution (priority order)

spiceguard locates the ngspice binary in this order:

  1. --ngspice PATH CLI flag — if given and not executable, raises an error immediately (no fallthrough)
  2. $NGSPICE environment variable — same hard-configured semantics; error if set but not usable
  3. which ngspice (PATH lookup)
  4. Legacy fallback /opt/homebrew/bin/ngspice

If none of the above resolves to a usable binary, spiceguard exits with code 3 and prints a clear message listing what was tried. Install ngspice, or point to it explicitly:

spiceguard --ngspice /usr/local/bin/ngspice mynetlist.cir
# or
export NGSPICE=/usr/local/bin/ngspice

CLI usage

spiceguard [--ngspice PATH] [--no-exec] [--version] [--help] FILE...
spiceguard kicad [--ngspice PATH] [--no-exec] FILE...

Pass one or more netlist (or schematic) files. When multiple files are given, spiceguard evaluates each in sequence and exits with the worst verdict across all.

Options

Flag Description
FILE... One or more netlist or schematic files to check
--ngspice PATH Explicit path to the ngspice binary
--json Emit results as a JSON array (for editors, CI, tooling)
--no-exec Strip .control blocks and file-splicing directives (.include/.inc*/.lib) before simulation — for netlists you did not write; applies to both the default review mode and spiceguard kicad
--version Print version and exit
--help Show usage

Exit codes

Code Meaning
0 TRUSTWORTHY — ngspice exited 0 and no trust issues found
1 FAILED — ngspice exited non-zero
2 SUSPECT — ngspice exited 0 but trust issues were detected
3 ngspice not found
64 Usage error (bad arguments)

Example

$ PYTHONPATH=src python3 -m spiceguard tests/netlists/n5_healthy_control.cir

======================================================================
n5_healthy_control.cir
======================================================================
✓  TRUSTWORTHY   (ngspice exit 0)

  No trust issues detected.
$ PYTHONPATH=src python3 -m spiceguard tests/netlists/n1_missing_ground.cir

======================================================================
n1_missing_ground.cir
======================================================================
⚠  SUSPECT   (ngspice exit 0)

  [FATAL] no_ground
  → No node '0' (ground). SPICE has no voltage reference, so it may float the circuit and return arbitrary WRONG voltages with no error. Fix: tie a reference node to '0'.

Input formats

Extension Handling
.cir, .net, .sp, .spice, .ckt Passed directly to ngspice
Any other netlist ngspice natively translates KiCad, LTspice, PSpice, HSpice dialects
.asc Converted in-process (experimental — see below)

LTspice .asc (experimental): spiceguard converts .asc schematics using built-in 2-pin symbol geometry (resistor, capacitor, inductor, diode, voltage source, current source). Net connectivity is recovered by union-find over wire/pin/flag coordinates. For anything beyond these built-in symbols, export the netlist from LTspice ("View > SPICE Netlist") and feed that instead — that path is exact. When a .asc is evaluated, the generated netlist is printed at the end of the report so you can compare it against LTspice's own export.

Subcircuit support (Feature B)

spiceguard's parser handles:

  • .subckt / .ends block collection and extraction
  • X-instance flattening with automatic node namespacing (internal nodes become instancename:node to avoid collisions)
  • + continuation lines rejoined before parsing
  • .include file resolution (local files only; URL .include lines are warned and skipped)

Errors detected during subcircuit processing:

  • undefined_subckt — X-instance references a subckt name not defined in the netlist
  • port_mismatch — X-instance provides a different number of nodes than the subckt port list
  • subckt_recursion — self- or mutual-referencing subckts (skipped with a warning)

KiCad workflow (Feature C)

spiceguard kicad myboard.cir

The kicad subcommand runs the standard trust check plus a KiCad-specific preflight that detects the classic export gotcha:

kicad_ground_not_zero — ngspice requires the circuit ground to be exactly node 0. KiCad schematics commonly use a GND net (or GNDA, VSS, 0V, AGND, DGND, PGND) that is NOT automatically mapped to node 0 on SPICE export. When such a net is present and node 0 is absent, the simulation silently floats the entire circuit, yielding wrong voltages with no error. The message includes KiCad-specific fix instructions (set node mapping in Symbol Properties or place a PWR_FLAG).

Pipe form — export and check in a single step without writing a file:

kicad-cli sch export netlist --format spice myboard.kicad_sch -o - \
  | spiceguard kicad -

Important: spiceguard is a command-line workflow helper, not a native KiCad plugin. KiCad has no post-simulation event API, so there is no in-GUI integration; run spiceguard kicad from your terminal or CI pipeline.


Verdict model

Every run produces one of three verdicts:

Verdict Meaning
TRUSTWORTHY ngspice exited 0 and no trust-breaking issues found
SUSPECT ngspice exited 0 but at least one FATAL/SILENT/WARN issue detected
FAILED ngspice exited non-zero

FAILED outranks SUSPECT, which outranks TRUSTWORTHY (this is why exit code 1 < 2 numerically but FAILED is the worst outcome).

Detectors

Code Severity Description
no_ground FATAL No node '0' — circuit has no voltage reference
source_conflict FATAL Multiple voltage sources forced across the same node pair
no_dc_path FATAL Node reachable only through capacitors/current sources — no DC reference
timestep_collapse FATAL Transient timestep collapsed; specific culprit identified from the log
singular_node FATAL Singular matrix at a node — no defined DC solution
singular_branch FATAL Singular matrix at a branch (current unconstrained)
silent_fallback SILENT ngspice exited 0 after gmin/source stepping failed; operating point is a relaxed estimate, not a true solution
kicad_ground_not_zero WARN KiCad export uses GND/GNDA/etc. but no node 0 (kicad subcommand only)
dangling_node WARN Node connects to only one pin — likely a wiring mistake

Integrations

Surface Where What it gives you
Docker docker/ Zero-setup image with ngspice bundled
VS Code vscode/ Inline trust diagnostics as you edit .cir/.net/.sp files (consumes --json)
KiCad kicad/ kicad-cli netlist export → spiceguard kicad, as a one-liner, helper script, or CI step
MCP server mcp-server/ Lets AI agents (Claude Code, Cursor, ...) verify SPICE netlists they generate before treating the result as ground truth

All four build on the same engine; the --json output makes spiceguard easy to wire into editors, CI, and other tools.

Security

spiceguard runs ngspice in batch mode on the netlists you give it. Treat a netlist like a script you are about to run, because in two ways it is one:

  • A SPICE netlist can contain .control/shell directives that execute arbitrary shell commands. The captured log may include their output.
  • .include reads whatever file path the netlist specifies (the same files ngspice itself would read), so a hostile netlist can point at files on your disk. The contents are parsed as a netlist, not printed.

Only run spiceguard on netlists you trust. It is a local dev/CI utility, not a sandbox — --no-exec is defense-in-depth against the two specific attack surfaces above, not a guarantee that a hostile netlist is safe to evaluate.

For untrusted netlists (AI-generated, PR-submitted) use --no-exec, which strips exactly these lines before simulation:

  • .control ... .endc blocks (arbitrary shell commands), and
  • file-splicing directives: .include and every ngspice-honored prefix abbreviation of it (.inc, .incl, .inclu, .includ), plus .lib (library file + section) and its .endl block terminator — i.e. anything that pulls another file's content into the netlist ngspice runs.

The MCP server does this unconditionally.

Hardening that is in place:

  • ngspice is invoked with --no-spiceinit, so a .spiceinit/spice.rc config planted next to the netlist is not auto-executed.
  • The simulator is launched with a fixed argument list, never through a shell (no shell=True), so the path can't be shell-injected.
  • A 120s timeout terminates a hung/non-converging run (reported as FAILED, not a crash); temp files use mkstemp (0600) and are always cleaned up; subcircuit nesting is depth-bounded against stack exhaustion.
  • spiceguard has no third-party runtime dependencies, and the source passes bandit -r src/ with no findings (pip-audit reports no vulnerable deps).

Limitations (honest scope)

  • Parser scope: The static netlist parser covers the element types listed in netlist.py (NODE_COUNT). Exotic or simulator-specific elements not in that table are silently skipped (they do not affect the running netlist — ngspice still sees the full file).
  • .asc is experimental and scoped to 6 built-in 2-pin symbol types. Any schematic with transistors, op-amps, subcircuit blocks, or custom symbols must be exported from LTspice first. Always verify the generated netlist against LTspice's own "View > SPICE Netlist".
  • Legacy fallback (/opt/homebrew/bin/ngspice) is tier-4 last-resort only. It is not the preferred path; prefer PATH or the $NGSPICE variable.
  • KiCad integration is a CLI helper, not an in-application plugin.

Development

Run the test suite (no ngspice required for unit tests; integration tests auto-skip when ngspice is absent):

PYTHONPATH=src python3 -m pytest -q

Observed output on the current suite:

119 passed in 2.61s

Metadata

Release files for spiceguard 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for spiceguard 0.3.0
File Size Uploaded
spiceguard-0.3.0.tar.gz 50.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for spiceguard 0.3.0
File Interpreter ABI Platform
spiceguard-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 81.2 kB

Release files / spiceguard-0.3.0.tar.gz

Download URL spiceguard-0.3.0.tar.gz
Size 50.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2b0612e22e24cf5118e53432e5df647bd37e683ea6fd3510e51d52f948cbe499
BLAKE2b-256 checksum
How to use checksums
9cae0b1a624576b3d370eb35091c0c20642eb347d3d392f44bf6ecefb205a2a6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / spiceguard-0.3.0-py3-none-any.whl

Download URL spiceguard-0.3.0-py3-none-any.whl
Size 31.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0a6f0c392874eaffca87b6ad66c0165b946f519a0ca3d7b6aa58276dd28c04cd
BLAKE2b-256 checksum
How to use checksums
b0d1e63b9a0a63174536c2f840af90d4dd865e8e32ead2e03f8d028736a1ee5a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page