fix: address round 2 review findings from issue #81

- Move atomic mv inside gardener loop so reply is only claimed when a
  matching needs_human session exists (fixes reply-loss regression)
- Delay rm of claimed file until after successful injection in both
  supervisor and gardener (OOM/SIGKILL leaves file recoverable)
- Fix matrix_listener ack message: 'next poll' instead of 'next supervisor poll'

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-17 22:59:05 +00:00
parent bfe0c09b5c
commit 63e60de9d6
3 changed files with 30 additions and 31 deletions

View file

@ -66,23 +66,24 @@ fi
# ── Inject human replies into needs_human dev sessions (backup to supervisor) ─ # ── Inject human replies into needs_human dev sessions (backup to supervisor) ─
HUMAN_REPLY_FILE="/tmp/dev-escalation-reply" HUMAN_REPLY_FILE="/tmp/dev-escalation-reply"
_gr_claimed="/tmp/dev-escalation-reply.gardener.$$" for _gr_phase_file in /tmp/dev-session-"${PROJECT_NAME}"-*.phase; do
if [ -s "$HUMAN_REPLY_FILE" ] && mv "$HUMAN_REPLY_FILE" "$_gr_claimed" 2>/dev/null; then [ -f "$_gr_phase_file" ] || continue
_gr_phase=$(head -1 "$_gr_phase_file" 2>/dev/null | tr -d '[:space:]' || true)
[ "$_gr_phase" = "PHASE:needs_human" ] || continue
_gr_issue=$(basename "$_gr_phase_file" .phase)
_gr_issue="${_gr_issue#dev-session-${PROJECT_NAME}-}"
[ -z "$_gr_issue" ] && continue
_gr_session="dev-${PROJECT_NAME}-${_gr_issue}"
tmux has-session -t "$_gr_session" 2>/dev/null || continue
# Atomic claim — only take the file once we know a session needs it
_gr_claimed="/tmp/dev-escalation-reply.gardener.$$"
[ -s "$HUMAN_REPLY_FILE" ] && mv "$HUMAN_REPLY_FILE" "$_gr_claimed" 2>/dev/null || continue
_gr_reply=$(cat "$_gr_claimed") _gr_reply=$(cat "$_gr_claimed")
rm -f "$_gr_claimed"
for _gr_phase_file in /tmp/dev-session-"${PROJECT_NAME}"-*.phase; do
[ -f "$_gr_phase_file" ] || continue
_gr_phase=$(head -1 "$_gr_phase_file" 2>/dev/null | tr -d '[:space:]' || true)
[ "$_gr_phase" = "PHASE:needs_human" ] || continue
_gr_issue=$(basename "$_gr_phase_file" .phase) _gr_inject_msg="Human reply received for issue #${_gr_issue}:
_gr_issue="${_gr_issue#dev-session-${PROJECT_NAME}-}"
[ -z "$_gr_issue" ] && continue
_gr_session="dev-${PROJECT_NAME}-${_gr_issue}"
tmux has-session -t "$_gr_session" 2>/dev/null || continue
_gr_inject_msg="Human reply received for issue #${_gr_issue}:
${_gr_reply} ${_gr_reply}
@ -91,20 +92,19 @@ Instructions:
2. Continue your work based on their input. 2. Continue your work based on their input.
3. When done, push your changes and write the appropriate phase." 3. When done, push your changes and write the appropriate phase."
_gr_tmpfile=$(mktemp /tmp/human-inject-XXXXXX) _gr_tmpfile=$(mktemp /tmp/human-inject-XXXXXX)
printf '%s' "$_gr_inject_msg" > "$_gr_tmpfile" printf '%s' "$_gr_inject_msg" > "$_gr_tmpfile"
tmux load-buffer -b "human-inject-${_gr_issue}" "$_gr_tmpfile" || true tmux load-buffer -b "human-inject-${_gr_issue}" "$_gr_tmpfile" || true
tmux paste-buffer -t "$_gr_session" -b "human-inject-${_gr_issue}" || true tmux paste-buffer -t "$_gr_session" -b "human-inject-${_gr_issue}" || true
sleep 0.5 sleep 0.5
tmux send-keys -t "$_gr_session" "" Enter || true tmux send-keys -t "$_gr_session" "" Enter || true
tmux delete-buffer -b "human-inject-${_gr_issue}" 2>/dev/null || true tmux delete-buffer -b "human-inject-${_gr_issue}" 2>/dev/null || true
rm -f "$_gr_tmpfile" rm -f "$_gr_tmpfile" "$_gr_claimed"
rm -f "/tmp/dev-renotify-${PROJECT_NAME}-${_gr_issue}" rm -f "/tmp/dev-renotify-${PROJECT_NAME}-${_gr_issue}"
log "${PROJECT_NAME}: #${_gr_issue} human reply injected into session ${_gr_session} (gardener)" log "${PROJECT_NAME}: #${_gr_issue} human reply injected into session ${_gr_session} (gardener)"
break # only one reply to deliver break # only one reply to deliver
done done
fi
# ── Fetch all open issues ───────────────────────────────────────────────── # ── Fetch all open issues ─────────────────────────────────────────────────
ISSUES_JSON=$(codeberg_api GET "/issues?state=open&type=issues&limit=50&sort=updated&direction=desc" 2>/dev/null || true) ISSUES_JSON=$(codeberg_api GET "/issues?state=open&type=issues&limit=50&sort=updated&direction=desc" 2>/dev/null || true)

View file

@ -143,7 +143,7 @@ while true; do
;; ;;
dev) dev)
printf '%s\t%s\t%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$SENDER" "$BODY" >> /tmp/dev-escalation-reply printf '%s\t%s\t%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$SENDER" "$BODY" >> /tmp/dev-escalation-reply
matrix_send "dev" "✓ received, will inject on next supervisor poll" "$THREAD_ROOT" >/dev/null 2>&1 || true matrix_send "dev" "✓ received, will inject on next poll" "$THREAD_ROOT" >/dev/null 2>&1 || true
;; ;;
vault) vault)
# Parse APPROVE <id> or REJECT <id> from reply # Parse APPROVE <id> or REJECT <id> from reply

View file

@ -540,7 +540,6 @@ check_project() {
_nh_claimed="/tmp/dev-escalation-reply.supervisor.$$" _nh_claimed="/tmp/dev-escalation-reply.supervisor.$$"
if [ -s "$HUMAN_REPLY_FILE" ] && mv "$HUMAN_REPLY_FILE" "$_nh_claimed" 2>/dev/null; then if [ -s "$HUMAN_REPLY_FILE" ] && mv "$HUMAN_REPLY_FILE" "$_nh_claimed" 2>/dev/null; then
_nh_reply=$(cat "$_nh_claimed") _nh_reply=$(cat "$_nh_claimed")
rm -f "$_nh_claimed"
_nh_inject_msg="Human reply received for issue #${_nh_issue}: _nh_inject_msg="Human reply received for issue #${_nh_issue}:
${_nh_reply} ${_nh_reply}
@ -558,7 +557,7 @@ Instructions:
sleep 0.5 sleep 0.5
tmux send-keys -t "$_nh_session" "" Enter || true tmux send-keys -t "$_nh_session" "" Enter || true
tmux delete-buffer -b "human-inject-${_nh_issue}" 2>/dev/null || true tmux delete-buffer -b "human-inject-${_nh_issue}" 2>/dev/null || true
rm -f "$_nh_tmpfile" rm -f "$_nh_tmpfile" "$_nh_claimed"
rm -f "/tmp/dev-renotify-${proj_name}-${_nh_issue}" rm -f "/tmp/dev-renotify-${proj_name}-${_nh_issue}"
flog "${proj_name}: #${_nh_issue} human reply injected into session ${_nh_session}" flog "${proj_name}: #${_nh_issue} human reply injected into session ${_nh_session}"