fix(issue-19): close the trap race, select daemons by argv shape, align docs with the polling fallback
Review round 1 (claude PASS, agy PASS) recommendations: - reconcile.sh: `_sub_stop` falls back to `$!` for the window before `_sub_pid=$!` (R-1); lock identity compared as (st_dev, st_ino). - lib.sh legacy start_watchdog prints nothing when opted out and no daemon runs, byte-identical to the python body. - conftest/test: `_is_subscriber_daemon_argv` selects only `bash|sh <root>/.../reconcile.sh --subscribe`, never a brief-carrying wrapper that quotes the words or a prefix-colliding sibling root (R-2); one new unit test for the selector. - monitor SKILL.md / VERSIONS.md: the polling fallback checks the lock file's presence only and does not exclude a second daemon; measurement wording uses the argv-shape criterion instead of raw pgrep. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
1 parent
4da4a50682
commit
c096cb455b
6 files changed
+69
-16
No files matched your search
@@ -79,9 +79,12 @@ if [ "$SUBSCRIBE" = "1" ]; then
|
||||
_sub_pid=""
|
||||
_sub_stop() {
|
||||
trap - TERM INT
|
||||
if [ -n "$_sub_pid" ]; then
|
||||
kill -TERM "$_sub_pid" 2>/dev/null || true
|
||||
wait "$_sub_pid" 2>/dev/null || true
|
||||
# `$!` covers the window between `&` and `_sub_pid=$!` (review R-1); a
|
||||
# child that already exited makes kill/wait harmless no-ops.
|
||||
local child="${_sub_pid:-${!:-}}"
|
||||
if [ -n "$child" ]; then
|
||||
kill -TERM "$child" 2>/dev/null || true
|
||||
wait "$child" 2>/dev/null || true
|
||||
fi
|
||||
exit 143
|
||||
}
|
||||
@@ -115,7 +118,8 @@ except BlockingIOError:
|
||||
except Exception as e:
|
||||
print(f"MQTT Monitor: failed to acquire monitor lock ({e}). Exiting.", flush=True)
|
||||
sys.exit(1)
|
||||
lock_ino = os.fstat(lock_file.fileno()).st_ino
|
||||
_lock_st = os.fstat(lock_file.fileno())
|
||||
lock_ident = (_lock_st.st_dev, _lock_st.st_ino)
|
||||
|
||||
|
||||
def _workspace_gone():
|
||||
@@ -131,7 +135,8 @@ def _workspace_gone():
|
||||
if not os.path.isdir(root):
|
||||
return f"workspace {root} no longer exists"
|
||||
try:
|
||||
if os.stat(lock_file_path).st_ino != lock_ino:
|
||||
st = os.stat(lock_file_path)
|
||||
if (st.st_dev, st.st_ino) != lock_ident:
|
||||
return f"{lock_file_path} was replaced"
|
||||
except OSError:
|
||||
return f"{lock_file_path} no longer exists"
|
||||
@@ -367,6 +372,9 @@ PYEOF
|
||||
_start=$(date +%s)
|
||||
# Issue #19: same lifecycle as the MQTT loop - stop when the workspace is
|
||||
# gone, and sleep in a waited child so SIGTERM/SIGINT are not deferred.
|
||||
# The python child released .mam/monitor.lock when it exited, so this
|
||||
# fallback checks the lock file's *presence* only and does not exclude a
|
||||
# second daemon (pre-existing behaviour of the fallback).
|
||||
trap 'trap - TERM INT; exit 143' TERM INT
|
||||
while :; do
|
||||
if [ ! -d "$WORKSPACE_ROOT" ] || [ ! -f "$WORKSPACE_ROOT/.mam/monitor.lock" ]; then
|
||||
|
||||
Reference in new issue
Block a user