Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Safe Python Netmiko patterns for read-only collection, bounded batch SSH, TextFSM parsing, guarded config changes, timeouts, and network automation error handling.
.claude/skills/netmiko-ssh-automation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-19 | ✗→✓ | ▲ Improved | — | — |
| case-08 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
Use this skill when writing or reviewing Python automation that connects to network devices with Netmiko. Keep the default path read-only; config changes need a separate change window, peer review, and rollback plan.
show command output across routers, switches, or firewalls.send_command() collection.getpass; never hardcode credentials.send_config_set().save_config() until the change has been verified and approved.pythonimport os from getpass import getpass from netmiko import ConnectHandler from netmiko.exceptions import ( NetmikoAuthenticationException, NetmikoTimeoutException, ReadTimeout, ) device = { "device_type": "cisco_ios", "host": "192.0.2.10", "username": os.environ.get("NETMIKO_USERNAME") or input("Username: "), "password": os.environ.get("NETMIKO_PASSWORD") or getpass("Password: "), "secret": os.environ.get("NETMIKO_ENABLE_SECRET"), "conn_timeout": 10, "auth_timeout": 20, "banner_timeout": 15, "read_timeout_override": 30, } try: with ConnectHandler(**device) as conn: if device.get("secret") and not conn.check_enable_mode(): conn.enable() output = conn.send_command("show ip interface brief", read_timeout=30) print(output) except NetmikoAuthenticationException: print("Authentication failed") except NetmikoTimeoutException: print("SSH connection timed out") except ReadTimeout: print("Command read timed out")
Use placeholder addresses from documentation ranges in examples. Keep real inventory in an ignored local file or a secrets-managed system.
pythonfrom concurrent.futures import ThreadPoolExecutor, as_completed from typing import Any def collect_show(device: dict[str, Any], command: str) -> dict[str, Any]: host = device["host"] try: with ConnectHandler(**device) as conn: output = conn.send_command(command, read_timeout=45) return {"host": host, "ok": True, "output": output} except (NetmikoAuthenticationException, NetmikoTimeoutException, ReadTimeout) as exc: return {"host": host, "ok": False, "error": type(exc).__name__} results = [] with ThreadPoolExecutor(max_workers=8) as pool: futures = [pool.submit(collect_show, device, "show version") for device in devices] for future in as_completed(futures): results.append(future.result())
Keep max_workers low unless the device estate and AAA systems are known to handle higher connection volume.
Netmiko can ask TextFSM, TTP, or Genie to parse supported command output. Treat parser output as an optimization, not the only evidence path.
pythonwith ConnectHandler(**device) as conn: parsed = conn.send_command( "show ip interface brief", use_textfsm=True, raise_parsing_error=False, read_timeout=30, ) if isinstance(parsed, str): print("No parser template matched; store raw output for review") else: for row in parsed: print(row)
If parsing drives a blocking decision, keep the raw command output alongside the parsed result so an operator can inspect mismatches.
pythonimport os commands = [ "interface GigabitEthernet0/1", "description CHANGE-1234 UPLINK-TO-CORE", ] apply_changes = os.environ.get("APPLY_NETWORK_CHANGES") == "1" if not apply_changes: print("Dry run only. Candidate commands:") print("\n".join(commands)) else: with ConnectHandler(**device) as conn: conn.enable() before = conn.send_command("show running-config interface GigabitEthernet0/1") output = conn.send_config_set(commands) after = conn.send_command("show running-config interface GigabitEthernet0/1") print(before) print(output) print(after) print("Verify behavior before saving startup config.")
Saving the config is a separate approval step. In production, include a rollback snippet and capture before/after evidence in the change record.
conn_timeout, auth_timeout, and command read_timeout set?save_config() separate from the initial push and tied to verification?cisco-ios-patternsnetwork-config-validationnetwork-interface-health| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
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. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.