fix(watchdog): clear stale .keep-running sentinel on startup

Follow-up to #402. The sentinel is only removed inside the grace-period
"sentinel found" branch. When the HTTP /watchdog/disable request wins
the race (normal case on macOS/Linux, occasional on Windows), the
_watchdog_disabled=True check returns first and the sentinel is left on
disk indefinitely.

If a later session spawns a fresh server and the user exits without
"keep running", the new watchdog would find that stale sentinel during
its grace period and keep the server alive against user intent.

Wipe any pre-existing sentinel when the watchdog starts so only signals
written during this session's lifetime can influence grace-period
decisions.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
James Pine
2026-04-16 01:57:05 -07:00
co-authored by Claude Opus 4.6
parent 479bc7fc5e
commit 7184a25e44
+13
View File
@@ -169,6 +169,19 @@ def _start_parent_watchdog(parent_pid, data_dir=None):
if not alive:
watchdog_logger.warning(f"Parent PID {parent_pid} not found on first check — disabling watchdog")
return
# Clear any stale .keep-running sentinel from a previous session. The
# sentinel is only removed by the watchdog when it's consumed during a
# grace period; if the HTTP /watchdog/disable path wins the race on a
# "keep running" exit, the sentinel is left on disk. Wipe it here so a
# future session can't inherit that stale signal.
if data_dir:
stale = os.path.join(data_dir, ".keep-running")
if os.path.exists(stale):
try:
os.remove(stale)
watchdog_logger.info("Removed stale .keep-running sentinel from previous session")
except OSError as e:
watchdog_logger.warning(f"Failed to remove stale sentinel: {e}")
while True:
if _watchdog_disabled:
watchdog_logger.info("Watchdog disabled (keep server running), stopping monitor")