skill-creator: eval-viewer sends SIGTERM to any process on the target port

Author: SmartNightlyCreated Sep 16, 2026Updated Sep 16, 2026

generate_review.py terminates every process listening on its target port before starting the viewer, with no check that the process is a previous eval-viewer instance, no prompt, and no output.

File: plugins/skill-creator/skills/skill-creator/eval-viewer/generate_review.py, L288–L305, called at L440. Confirmed against main at da64a66.

python
def _kill_port(port: int) -> None:
    """Kill any process listening on the given port."""
    try:
        result = subprocess.run(
            ["lsof", "-ti", f":{port}"],
            capture_output=True, text=True, timeout=5,
        )
        for pid_str in result.stdout.strip().split("\n"):
            if pid_str.strip():
                try:
                    os.kill(int(pid_str.strip()), signal.SIGTERM)
                except (ProcessLookupError, ValueError):
                    pass

The only print in this function fires when lsof itself is missing, so the user is never told that a process was killed.

The default port is 3117 (L390), which makes an accidental hit unlikely. But --port is user-settable and gets passed by whoever is driving the script, so --port 3000, --port 8080 or --port 5432 silently kills a dev server, an app, or a local Postgres. Since the script is typically invoked by an agent on the user's behalf, the port isn't always a value the user chose themselves.

The kill is also redundant

L444 already handles a busy port:

python
    except OSError:
        # Port still in use after kill attempt — find a free one
        server = HTTPServer(("127.0.0.1", 0), handler)
        port = server.server_address[1]

Binding fails, the fallback picks a free port, and the viewer starts anyway. So the SIGTERM buys nothing that the existing error path doesn't already cover.

Suggested fix

Delete _kill_port and its call at L440, and let the OSError fallback do the work. If reclaiming a stale viewer's port is genuinely wanted, gate it on the process actually being an earlier eval-viewer (match the cmdline) and print what is being terminated.

How this was found

Scanning my locally installed skills with NVIDIA SkillSpector v2.11.2:

bash
uv tool install git+https://github.com/NVIDIA/skillspector.git
skillspector scan <path>/plugins/skill-creator/skills/skill-creator

For calibration: this was one of only two substantive findings across 31 scanned skills — the scanner's static stage is very noisy on documentation skills, and its LLM stage suppressed none of that noise. I verified every finding by hand; the rest were false positives.

Source: anthropics/claude-plugins-official