← Back to posts
AI & Automation

Using Claude to Automate Cisco IOS-XE Configuration and Troubleshooting

Why Claude Changes Network Automation

Traditional network automation with tools like Ansible, Netmiko, or NAPALM is powerful but rigid β€” it executes exactly what you tell it to. The moment a task requires reasoning (interpreting log output, generating config from a description, deciding what changed), you’re back to writing logic by hand.

Claude adds the reasoning layer. You can describe what you want in plain English and get working IOS-XE configuration back. You can paste raw show command output and get a structured analysis. You can build a troubleshooting assistant that walks engineers through diagnosis step by step.

This post covers practical patterns for using Claude with Cisco IOS-XE.


Setup

import anthropic
import subprocess
from netmiko import ConnectHandler

client = anthropic.Anthropic()  # Uses ANTHROPIC_API_KEY env var

def ask_claude(prompt: str, system: str = None) -> str:
    kwargs = {
        "model": "claude-opus-4-5",
        "max_tokens": 2048,
        "messages": [{"role": "user", "content": prompt}]
    }
    if system:
        kwargs["system"] = system
    msg = client.messages.create(**kwargs)
    return msg.content[0].text

Pattern 1: Natural Language to IOS-XE Config

Describe what you need, get production-ready configuration back:

def generate_iosxe_config(description: str, device_info: dict = None) -> str:
    context = ""
    if device_info:
        context = f"""
Device context:
- Platform: {device_info.get('platform', 'IOS-XE')}
- Version: {device_info.get('version', 'unknown')}
- Role: {device_info.get('role', 'unknown')}
"""
    prompt = f"""
Generate Cisco IOS-XE configuration for the following requirement:

{description}
{context}
Requirements:
- Output ONLY the configuration commands, no explanations
- Use best practices (explicit deny, logging, descriptions)
- Include any prerequisite commands needed
- Add interface descriptions where applicable
"""
    return ask_claude(prompt, system="You are a senior Cisco network engineer. Output only IOS-XE CLI commands.")

# Examples
bgp_config = generate_iosxe_config("""
Configure eBGP peering with upstream provider:
- Local ASN: 65001
- Neighbor: 203.0.113.1, remote ASN 64512
- Authenticate with MD5 key 'Sup3rS3cur3'
- Advertise 10.0.0.0/8 and 172.16.0.0/12
- Set local-preference 200 for all received routes
- Enable soft-reconfiguration inbound
""")
print(bgp_config)

Sample output:

router bgp 65001
 bgp log-neighbor-changes
 neighbor 203.0.113.1 remote-as 64512
 neighbor 203.0.113.1 description UPSTREAM-PROVIDER
 neighbor 203.0.113.1 password Sup3rS3cur3
 !
 address-family ipv4
  network 10.0.0.0 mask 255.0.0.0
  network 172.16.0.0 mask 255.240.0.0
  neighbor 203.0.113.1 activate
  neighbor 203.0.113.1 soft-reconfiguration inbound
  neighbor 203.0.113.1 route-map SET-LOCAL-PREF in
 exit-address-family
!
route-map SET-LOCAL-PREF permit 10
 set local-preference 200
!
ip prefix-list ADVERTISED seq 5 permit 10.0.0.0/8
ip prefix-list ADVERTISED seq 10 permit 172.16.0.0/12

Pattern 2: Parse Show Command Output

Feed raw CLI output to Claude and get structured, actionable data back:

def analyze_show_output(command: str, output: str) -> dict:
    prompt = f"""
Analyze this IOS-XE '{command}' output and return a JSON object with:
- summary: one sentence describing the state
- issues: list of problems found (empty list if none)
- recommendations: list of recommended actions
- key_metrics: dict of important values extracted

Output ONLY valid JSON, no markdown, no explanation.

Output:
{output}
"""
    import json
    response = ask_claude(prompt)
    try:
        return json.loads(response)
    except json.JSONDecodeError:
        # Strip any accidental markdown
        clean = response.strip().strip('`').replace('json\n', '', 1)
        return json.loads(clean)

# Connect and pull data
device = {
    "device_type": "cisco_ios",
    "host": "192.168.1.1",
    "username": "admin",
    "password": "password",
}

with ConnectHandler(**device) as conn:
    bgp_output = conn.send_command("show ip bgp summary")
    
analysis = analyze_show_output("show ip bgp summary", bgp_output)
print(analysis)
# {
#   "summary": "BGP has 3 neighbors, 2 established and 1 in Active state",
#   "issues": ["Neighbor 10.0.0.2 is in Active state - not established"],
#   "recommendations": ["Check physical connectivity to 10.0.0.2", 
#                       "Verify BGP password matches on both ends"],
#   "key_metrics": {"total_neighbors": 3, "established": 2, "prefixes_received": 45}
# }

Pattern 3: Configuration Audit and Compliance

Check running configs against your security standards:

def audit_config(running_config: str, standards: list[str]) -> dict:
    standards_text = "\n".join(f"- {s}" for s in standards)
    
    prompt = f"""
Audit this IOS-XE running configuration against the following security standards.
Return JSON with:
- compliant: list of standards that PASS
- violations: list of dicts with 'standard', 'finding', and 'remediation'
- risk_score: integer 0-100 (100 = highest risk)

Standards to check:
{standards_text}

Running Config:
{running_config}

Output ONLY valid JSON.
"""
    import json
    return json.loads(ask_claude(prompt))

# Usage
with ConnectHandler(**device) as conn:
    config = conn.send_command("show running-config")

standards = [
    "SSH version 2 must be enabled, Telnet must be disabled",
    "All VTY lines must have ACL applied",
    "NTP must be configured with authentication",
    "SNMP v3 only - SNMPv1/v2c must not be configured",
    "Service password-encryption must be enabled",
    "Banner motd must be configured",
    "Logging must be enabled with minimum level warnings",
    "ip ssh time-out must be 60 or less",
    "No IP source routing",
    "Unused interfaces must be shutdown and in unused VLAN",
]

audit = audit_config(config, standards)
print(f"Risk Score: {audit['risk_score']}/100")
for v in audit['violations']:
    print(f"FAIL: {v['standard']}")
    print(f"  Finding: {v['finding']}")
    print(f"  Fix: {v['remediation']}")

Pattern 4: Intelligent Troubleshooting Assistant

Build a multi-turn assistant that guides your NOC through diagnosis:

class IosXeTroubleshooter:
    def __init__(self, device_host: str, device_info: dict):
        self.host = device_host
        self.device_info = device_info
        self.history = []
        self.system = """You are a senior Cisco network engineer specializing in IOS-XE troubleshooting.
Guide the engineer step by step. For each step:
1. Ask for ONE specific 'show' command to run
2. When given the output, analyze it and either identify the root cause or ask for the next command
3. Once you identify the issue, provide the exact fix commands
Be concise and action-oriented."""

    def chat(self, user_input: str) -> str:
        self.history.append({"role": "user", "content": user_input})
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=1024,
            system=self.system,
            messages=self.history
        )
        reply = response.content[0].text
        self.history.append({"role": "assistant", "content": reply})
        return reply

# Usage
ts = IosXeTroubleshooter("192.168.1.1", {"role": "core-router", "version": "17.6"})

print(ts.chat("BGP neighbor 203.0.113.1 keeps flapping. It was stable for months."))
# Claude: "Let's start by checking the current BGP state. Please run:
#          show ip bgp neighbors 203.0.113.1 | include BGP state|Last reset|notification"

with ConnectHandler(**device) as conn:
    output = conn.send_command("show ip bgp neighbors 203.0.113.1 | include BGP state|Last reset|notification")

print(ts.chat(f"Here's the output:\n{output}"))
# Claude: "The hold timer expired notification suggests a keepalive timing issue.
#          Please run: show ip bgp neighbors 203.0.113.1 | include Hold time|Keepalive"

Pattern 5: Bulk Config Generation from Spreadsheet

Generate configs for dozens of devices from a data source:

import csv

def bulk_interface_config(csv_data: str) -> dict[str, str]:
    """Generate per-device interface configs from CSV data"""
    prompt = f"""
Generate IOS-XE interface configurations for each row in this CSV data.
Each row represents one switch port to configure.
Return a JSON object where keys are hostnames and values are the complete
interface configuration block for that device.

CSV Data (hostname, interface, vlan, description, port_type):
{csv_data}

Rules:
- access ports: switchport mode access, switchport access vlan X, spanning-tree portfast
- trunk ports: switchport mode trunk, switchport trunk allowed vlan X
- Always add description
- Always add 'no shutdown'

Output ONLY valid JSON.
"""
    import json
    return json.loads(ask_claude(prompt))

# Example CSV
csv_data = """hostname,interface,vlan,description,port_type
sw-floor1,GigabitEthernet1/0/1,100,WORKSTATION-DESK-101,access
sw-floor1,GigabitEthernet1/0/2,200,VOIP-PHONE-101,access
sw-floor1,GigabitEthernet1/0/48,100-200,UPLINK-TO-CORE,trunk
sw-floor2,GigabitEthernet1/0/1,100,WORKSTATION-DESK-201,access"""

configs = bulk_interface_config(csv_data)
for hostname, config in configs.items():
    print(f"\n=== {hostname} ===")
    print(config)

Best Practices

Always use a dry-run step. Before pushing any Claude-generated config, pipe it through show commands or use commit check equivalent:

def safe_push(conn, config_lines: list[str], dry_run: bool = True) -> dict:
    if dry_run:
        # Just print what would be sent
        print("[DRY RUN] Would push:")
        for line in config_lines:
            print(f"  {line}")
        return {"status": "dry_run"}
    
    output = conn.send_config_set(config_lines)
    return {"status": "pushed", "output": output}

Log everything Claude generates. Store prompts, responses, and push results with timestamps for audit trails.

Set Claude’s temperature to 0 for config generation. Deterministic output matters when generating CLI commands. The Claude API defaults to a reasonable temperature β€” for config generation, consistency is more important than creativity.

// Found this useful? Share it or start a conversation.