Kill processes holding the serial port. ⚠️ CRITICAL: MUST BE CALLED INSIDE DAEMON LOCK. Only kills orphaned processes from crashed sessions. Assumes caller has exclusive daemon lock (any port user is an orphan). This function uses process name and command-line pattern matching
(port: str)
| 221 | |
| 222 | |
| 223 | def kill_port_users(port: str) -> None: |
| 224 | """Kill processes holding the serial port. |
| 225 | |
| 226 | ⚠️ CRITICAL: MUST BE CALLED INSIDE DAEMON LOCK. |
| 227 | Only kills orphaned processes from crashed sessions. |
| 228 | Assumes caller has exclusive daemon lock (any port user is an orphan). |
| 229 | |
| 230 | This function uses process name and command-line pattern matching |
| 231 | (not open file handles) because on Windows, COM ports don't typically |
| 232 | show up as file handles via psutil.Process.open_files(). |
| 233 | |
| 234 | Safety features: |
| 235 | - Never kills current process or any parent process |
| 236 | - Never kills Python processes (could be agent backend) |
| 237 | - Only kills known serial tools (pio, esptool, miniterm, etc.) |
| 238 | - Logs all actions for auditing |
| 239 | |
| 240 | Args: |
| 241 | port: Serial port name (e.g., "COM4", "/dev/ttyUSB0") |
| 242 | """ |
| 243 | port_lower = port.lower() |
| 244 | processes_to_kill: list[tuple[Process, str, list[str]]] = [] |
| 245 | |
| 246 | # Get current process and its entire process tree (ancestors) |
| 247 | # We must NEVER kill ourselves or any parent process |
| 248 | current_pid = os.getpid() |
| 249 | protected_pids = {current_pid} |
| 250 | |
| 251 | try: |
| 252 | current_proc = psutil.Process(current_pid) |
| 253 | # Walk up the process tree and protect all ancestors |
| 254 | parent = current_proc.parent() |
| 255 | while parent: |
| 256 | protected_pids.add(parent.pid) |
| 257 | parent = parent.parent() |
| 258 | except (psutil.NoSuchProcess, psutil.AccessDenied): |
| 259 | pass |
| 260 | |
| 261 | # Define patterns for serial port users |
| 262 | # Kill dedicated serial tools AND orphaned Python serial processes |
| 263 | # (but protect agent backend: clud, claude, node.exe, etc.) |
| 264 | safe_serial_exes = [ |
| 265 | "pio.exe", |
| 266 | "pio", |
| 267 | "esptool.exe", |
| 268 | "esptool", |
| 269 | "platformio.exe", |
| 270 | "platformio", |
| 271 | "miniterm.exe", |
| 272 | "miniterm", |
| 273 | "putty.exe", |
| 274 | "putty", |
| 275 | "teraterm.exe", |
| 276 | "teraterm", |
| 277 | "screen", # Unix serial terminal |
| 278 | "cu", # Unix serial terminal |
| 279 | ] |
| 280 |
no test coverage detected