From d0135cd4f3bff134b361c6736f05bee5b0ed1666 Mon Sep 17 00:00:00 2001 From: Abdallah Hussien Date: Sun, 22 Mar 2026 02:25:53 +0300 Subject: [PATCH] fix: add timeout to _run_systemctl subprocess Add timeout=30 to subprocess.run calls in _systemd.py to prevent the CLI from blocking indefinitely if systemd hangs. Catches subprocess.TimeoutExpired and logs a warning. Closes #26 Co-Authored-By: Claude Opus 4.6 (1M context) --- forgewatch/cli/_systemd.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/forgewatch/cli/_systemd.py b/forgewatch/cli/_systemd.py index 81fe334..dc852fe 100644 --- a/forgewatch/cli/_systemd.py +++ b/forgewatch/cli/_systemd.py @@ -43,11 +43,21 @@ def _read_service_file(name: str) -> str: def _run_systemctl(*args: str) -> subprocess.CompletedProcess[bytes]: """Run a systemctl --user command and return the result.""" - return subprocess.run( - ["systemctl", "--user", *args], - check=False, - capture_output=True, - ) + try: + return subprocess.run( + ["systemctl", "--user", *args], + check=False, + capture_output=True, + timeout=30, + ) + except subprocess.TimeoutExpired: + warn(f"systemctl {' '.join(args)} timed out after 30 seconds") + return subprocess.CompletedProcess( + args=["systemctl", "--user", *args], + returncode=1, + stdout=b"", + stderr=b"timed out", + ) def install_service_files(*, include_indicator: bool = False) -> None: @@ -172,10 +182,14 @@ def disable(service: str) -> None: def print_status(service: str) -> None: """Print the status of a systemd user service (output goes directly to terminal).""" - subprocess.run( - ["systemctl", "--user", "status", service, "--no-pager"], - check=False, - ) + try: + subprocess.run( + ["systemctl", "--user", "status", service, "--no-pager"], + check=False, + timeout=30, + ) + except subprocess.TimeoutExpired: + warn(f"systemctl status {service} timed out after 30 seconds") def service_file_installed(service: str) -> bool: