Merge pull request 'fix: feat: supervisor cleans up orphaned tmux sessions + worktrees (#235)' (#239) from fix/issue-235 into main

This commit is contained in:
johba 2026-03-19 09:18:45 +01:00
commit e50d467172

View file

@ -680,6 +680,114 @@ Instructions:
fi fi
done done
# ===========================================================================
# P4-PROJECT: Orphaned tmux sessions — PR/issue closed externally
# ===========================================================================
status "P4: ${proj_name}: sweeping orphaned dev sessions"
while IFS= read -r _sess; do
[ -z "$_sess" ] && continue
# Extract issue number from dev-{project}-{issue}
_sess_issue="${_sess#dev-"${proj_name}"-}"
[[ "$_sess_issue" =~ ^[0-9]+$ ]] || continue
# Check Codeberg: is the issue still open?
_issue_state=$(codeberg_api GET "/issues/${_sess_issue}" 2>/dev/null \
| jq -r '.state // "open"' 2>/dev/null || echo "open")
_should_cleanup=false
_cleanup_reason=""
if [ "$_issue_state" = "closed" ]; then
_should_cleanup=true
_cleanup_reason="issue #${_sess_issue} closed externally"
else
# Issue still open — skip cleanup during active-wait phases (no PR yet is normal)
_phase_file="/tmp/dev-session-${proj_name}-${_sess_issue}.phase"
_curr_phase=$(head -1 "$_phase_file" 2>/dev/null | tr -d '[:space:]' || true)
case "${_curr_phase:-}" in
PHASE:needs_human|PHASE:awaiting_ci|PHASE:awaiting_review)
continue # session has legitimate pending work
;;
esac
# Check if associated PR is open (paginated)
_pr_branch="fix/issue-${_sess_issue}"
_has_open_pr=0
_pr_page=1
while true; do
_pr_page_json=$(codeberg_api GET "/pulls?state=open&limit=50&page=${_pr_page}" \
2>/dev/null || echo "[]")
_pr_page_len=$(printf '%s' "$_pr_page_json" | jq 'length' 2>/dev/null || echo 0)
_pr_match=$(printf '%s' "$_pr_page_json" | \
jq --arg b "$_pr_branch" '[.[] | select(.head.ref == $b)] | length' \
2>/dev/null || echo 0)
_has_open_pr=$(( _has_open_pr + ${_pr_match:-0} ))
[ "${_has_open_pr:-0}" -gt 0 ] && break
[ "${_pr_page_len:-0}" -lt 50 ] && break
_pr_page=$(( _pr_page + 1 ))
[ "$_pr_page" -gt 20 ] && break
done
if [ "$_has_open_pr" -eq 0 ]; then
# No open PR — check for a closed/merged PR with this branch (paginated)
_has_closed_pr=0
_pr_page=1
while true; do
_pr_page_json=$(codeberg_api GET "/pulls?state=closed&limit=50&page=${_pr_page}" \
2>/dev/null || echo "[]")
_pr_page_len=$(printf '%s' "$_pr_page_json" | jq 'length' 2>/dev/null || echo 0)
_pr_match=$(printf '%s' "$_pr_page_json" | \
jq --arg b "$_pr_branch" '[.[] | select(.head.ref == $b)] | length' \
2>/dev/null || echo 0)
_has_closed_pr=$(( _has_closed_pr + ${_pr_match:-0} ))
[ "${_has_closed_pr:-0}" -gt 0 ] && break
[ "${_pr_page_len:-0}" -lt 50 ] && break
_pr_page=$(( _pr_page + 1 ))
[ "$_pr_page" -gt 20 ] && break
done
if [ "$_has_closed_pr" -gt 0 ]; then
_should_cleanup=true
_cleanup_reason="PR for issue #${_sess_issue} is closed/merged"
else
# No PR at all — clean up if session idle >30min
# On query failure, skip rather than defaulting to epoch 0
if ! _sess_activity=$(tmux display-message -t "$_sess" \
-p '#{session_activity}' 2>/dev/null); then
flog "${proj_name}: Could not query activity for session ${_sess}, skipping"
continue
fi
_now_ts=$(date +%s)
_idle_min=$(( (_now_ts - _sess_activity) / 60 ))
if [ "$_idle_min" -gt 30 ]; then
_should_cleanup=true
_cleanup_reason="no PR found, session idle ${_idle_min}min"
fi
fi
fi
fi
if [ "$_should_cleanup" = true ]; then
tmux kill-session -t "$_sess" 2>/dev/null || true
_wt="/tmp/${proj_name}-worktree-${_sess_issue}"
if [ -d "$_wt" ]; then
git -C "$PROJECT_REPO_ROOT" worktree remove --force "$_wt" 2>/dev/null || true
fi
# Remove lock only if its recorded PID is no longer alive
_lock="/tmp/dev-agent-${proj_name}.lock"
if [ -f "$_lock" ]; then
_lock_pid=$(cat "$_lock" 2>/dev/null || true)
if [ -n "${_lock_pid:-}" ] && ! kill -0 "$_lock_pid" 2>/dev/null; then
rm -f "$_lock"
fi
fi
rm -f "/tmp/dev-session-${proj_name}-${_sess_issue}.phase"
fixed "${proj_name}: Cleaned orphaned session ${_sess} (${_cleanup_reason})"
fi
done < <(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep "^dev-${proj_name}-" || true)
# =========================================================================== # ===========================================================================
# P4-PROJECT: Clean stale worktrees for this project # P4-PROJECT: Clean stale worktrees for this project
# =========================================================================== # ===========================================================================