diff --git a/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh b/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh index 92c0def..ff6fefb 100755 --- a/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh +++ b/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh @@ -27,8 +27,10 @@ # Exit codes: # 0 = success (or already-stopped no-op) | 1 = YAML not found / not registered # 2 = invalid args | 3 = interactive confirmation required (--yes 누락) +# 4 = purge aborted (tmux session survived the kill chain) set -euo pipefail +# shellcheck disable=SC1091 source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh" usage() { @@ -68,7 +70,8 @@ done [ -n "$SESSION_NAME" ] || { echo "ERROR: --session required" >&2; usage; exit 2; } [ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found" >&2; exit 1; } -export TMUX_SERVER_NAME="$(resolve_tmux_server "$SESSION_NAME")" +TMUX_SERVER_NAME="$(resolve_tmux_server "$SESSION_NAME")" +export TMUX_SERVER_NAME # --agent 미지정 시 이름 suffix 로 fallback (P1-F) if [ -z "$AGENT" ]; then @@ -190,6 +193,21 @@ else echo "tmux already dead, just updating YAML" fi +# Purge pre-gate: 레코드 제거는 tmux 사망이 확인된 경우에만 허용한다. +# (kill 체인은 best-effort — 세션이 살아남으면 monitor drift-B 가 +# 레코드 없는 세션을 running 으로 자동 재등록해 purge 가 조용히 뒤집힌다) +if [ "$PURGE" = "1" ] && [ "$TMUX_ALIVE" = "1" ]; then + _wait_session_gone "$SESSION_NAME" 5 || true # SIGKILL 폴백의 비동기 회수 윈도우 흡수 + if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then + echo "ERROR: session '$SESSION_NAME' is still alive after the kill chain." >&2 + echo " Refusing registry removal — records preserved (no state was modified)." >&2 + echo " Diagnose the stuck TUI (tmux attach -t '$SESSION_NAME'), then re-run" >&2 + echo " stop_session.sh --purge-conversation --yes (retry is safe/idempotent)." >&2 + delegate_publish_event "$DELEGATE_JOB_ID" error "purge aborted: tmux session still alive" + exit 4 + fi +fi + atomic_dump_yaml "$AGENT_SESSIONS_YAML" \ SESSION_NAME="$SESSION_NAME" AGENT="$AGENT" PURGE="$PURGE" \ NOW_ISO="$NOW_ISO" NOW_EPOCH="$NOW_EPOCH" LAST_STATUS="$LAST_STATUS" \ @@ -216,12 +234,7 @@ if target is None: print(f"ERROR: disappeared during script: {name}", flush=True) raise SystemExit(1) -if purge: - target['status'] = 'terminated' - target['terminated_at'] = now - target['terminated_at_epoch'] = int(os.environ['NOW_EPOCH']) - target['termination_mode'] = 'purge' -else: +if not purge: target['status'] = 'stopped' target['stopped_at'] = now target['stopped_at_epoch'] = int(os.environ['NOW_EPOCH']) @@ -324,9 +337,13 @@ elif purge and not purge_uuid: print("WARN: --purge-conversation requested but no workspace-scoped UUID resolved; nothing purged", flush=True) if purge: - target['resumable'] = False - -print(f"updated: {name} status={target['status']}", flush=True) + d['tmux_sessions'] = [s for s in d.get('tmux_sessions', []) if s.get('name') != name] + if purge_uuid: + print(f"removed: {name} (registry entry fully purged from YAML+DB)", flush=True) + else: + print(f"removed: {name} (registry entry purged from YAML+DB; WARN: conversation artifacts unresolved — disk copies may remain)", flush=True) +else: + print(f"updated: {name} status={target['status']}", flush=True) PYEOF delegate_publish_event "$DELEGATE_JOB_ID" completed "session terminated"