From 02f483ff71a2f6d4c59c81805818655c0f91fef0 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 21 Mar 2026 07:38:39 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20matrix=5Flistener.sh=20drops=20dev/revie?= =?UTF-8?q?w=20injections=20=E2=80=94=20PROJECT=5FNAME=20is=20unset=20(#35?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- action/action-agent.sh | 2 +- dev/dev-agent.sh | 2 +- lib/env.sh | 2 +- lib/matrix_listener.sh | 69 ++++++++++++++++++++++++++++++------------ review/review-poll.sh | 6 ++-- 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/action/action-agent.sh b/action/action-agent.sh index 1bc7536..f8a8fe4 100644 --- a/action/action-agent.sh +++ b/action/action-agent.sh @@ -172,7 +172,7 @@ if [ -n "${_thread_id:-}" ]; then # Export for on-stop-matrix.sh hook (streams Claude output to thread) export MATRIX_THREAD_ID="$_thread_id" # Register thread root in map for listener dispatch (column 4 = issue number) - printf '%s\t%s\t%s\t%s\n' "$_thread_id" "action" "$(date +%s)" "${ISSUE}" \ + printf '%s\t%s\t%s\t%s\t%s\n' "$_thread_id" "action" "$(date +%s)" "${ISSUE}" "${PROJECT_NAME}" \ >> "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true fi diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index 0725a3d..abc61c3 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -736,7 +736,7 @@ if [ ! -f "${THREAD_FILE}" ] || [ -z "$(cat "$THREAD_FILE" 2>/dev/null)" ]; then if [ -n "${_thread_id:-}" ]; then printf '%s' "$_thread_id" > "$THREAD_FILE" # Register thread root in map for listener dispatch - printf '%s\t%s\t%s\t%s\n' "$_thread_id" "dev" "$(date +%s)" "${ISSUE}" >> "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true + printf '%s\t%s\t%s\t%s\t%s\n' "$_thread_id" "dev" "$(date +%s)" "${ISSUE}" "${PROJECT_NAME}" >> "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true fi fi notify "tmux session ${SESSION_NAME} started for issue #${ISSUE}: ${ISSUE_TITLE}" diff --git a/lib/env.sh b/lib/env.sh index a940da0..385cde4 100755 --- a/lib/env.sh +++ b/lib/env.sh @@ -127,7 +127,7 @@ matrix_send() { printf '%s' "$event_id" # Register thread root for listener dispatch (escalations only) if [ -z "$thread_id" ]; then - printf '%s\t%s\t%s\t%s\n' "$event_id" "$prefix" "$(date +%s)" "${ctx_tag}" >> "$MATRIX_THREAD_MAP" 2>/dev/null || true + printf '%s\t%s\t%s\t%s\t%s\n' "$event_id" "$prefix" "$(date +%s)" "${ctx_tag}" "${PROJECT_NAME:-}" >> "$MATRIX_THREAD_MAP" 2>/dev/null || true fi fi } diff --git a/lib/matrix_listener.sh b/lib/matrix_listener.sh index a6cd59f..efcf2fa 100755 --- a/lib/matrix_listener.sh +++ b/lib/matrix_listener.sh @@ -20,8 +20,21 @@ set -euo pipefail # Load shared environment source "$(dirname "$0")/../lib/env.sh" +# Pidfile guard — prevent duplicate listener processes +PIDFILE="/tmp/matrix-listener.pid" +if [ -f "$PIDFILE" ]; then + OLD_PID=$(cat "$PIDFILE") + if kill -0 "$OLD_PID" 2>/dev/null; then + echo "Listener already running (PID $OLD_PID)" >&2 + exit 0 + fi +fi +echo $$ > "$PIDFILE" +trap 'rm -f "$PIDFILE"' EXIT + SINCE_FILE="/tmp/matrix-listener-since" THREAD_MAP="${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" +ACKED_FILE="/tmp/matrix-listener-acked" LOGFILE="${FACTORY_ROOT}/supervisor/matrix-listener.log" SYNC_TIMEOUT=30000 # 30s long-poll BACKOFF=5 @@ -142,11 +155,13 @@ while true; do ;; dev) # Route reply into the dev tmux session using context_tag (issue number) + # Thread map columns: 1=thread_id, 2=agent, 3=timestamp, 4=issue, 5=project DEV_ISSUE=$(awk -F'\t' -v id="$THREAD_ROOT" '$1 == id {print $4}' "$THREAD_MAP" 2>/dev/null || true) + DEV_PROJECT=$(awk -F'\t' -v id="$THREAD_ROOT" '$1 == id {print $5}' "$THREAD_MAP" 2>/dev/null || true) DEV_INJECTED=false if [ -n "$DEV_ISSUE" ]; then - DEV_SESSION="dev-${PROJECT_NAME}-${DEV_ISSUE}" - DEV_PHASE_FILE="/tmp/dev-session-${PROJECT_NAME}-${DEV_ISSUE}.phase" + DEV_SESSION="dev-${DEV_PROJECT}-${DEV_ISSUE}" + DEV_PHASE_FILE="/tmp/dev-session-${DEV_PROJECT}-${DEV_ISSUE}.phase" if tmux has-session -t "$DEV_SESSION" 2>/dev/null; then DEV_CUR_PHASE=$(head -1 "$DEV_PHASE_FILE" 2>/dev/null | tr -d '[:space:]' || true) if [ "$DEV_CUR_PHASE" = "PHASE:needs_human" ] || [ "$DEV_CUR_PHASE" = "PHASE:awaiting_review" ]; then @@ -165,18 +180,22 @@ Consider this guidance for your current work." rm -f "$DEV_INJECT_TMP" DEV_INJECTED=true log "human guidance from ${SENDER} injected into ${DEV_SESSION}" - matrix_send "dev" "✓ guidance forwarded to dev session for issue #${DEV_ISSUE}" "$THREAD_ROOT" >/dev/null 2>&1 || true + # Reply on first successful injection only — no reply on subsequent ones + if ! grep -qF "$THREAD_ROOT" "$ACKED_FILE" 2>/dev/null; then + matrix_send "dev" "✓ Guidance forwarded to dev session for #${DEV_ISSUE}" "$THREAD_ROOT" >/dev/null 2>&1 || true + printf '%s\n' "$THREAD_ROOT" >> "$ACKED_FILE" + fi else - log "dev session ${DEV_SESSION} is busy (phase: ${DEV_CUR_PHASE:-active}), queuing" - matrix_send "dev" "✓ received — session is busy, will be available when dev pauses" "$THREAD_ROOT" >/dev/null 2>&1 || true + log "WARN: dev session '${DEV_SESSION}' busy (phase: ${DEV_CUR_PHASE:-active}), queuing message for issue #${DEV_ISSUE}" + matrix_send "dev" "❌ Could not inject: dev session for #${DEV_ISSUE} is busy (phase: ${DEV_CUR_PHASE:-active}), message queued" "$THREAD_ROOT" >/dev/null 2>&1 || true fi else - log "dev session ${DEV_SESSION} not found for issue #${DEV_ISSUE}" - matrix_send "dev" "dev session not active for issue #${DEV_ISSUE}" "$THREAD_ROOT" >/dev/null 2>&1 || true + log "WARN: tmux session '${DEV_SESSION}' not found for issue #${DEV_ISSUE} (project: ${DEV_PROJECT:-UNSET})" + matrix_send "dev" "❌ Could not inject: tmux session '${DEV_SESSION}' not found (project: ${DEV_PROJECT:-UNSET})" "$THREAD_ROOT" >/dev/null 2>&1 || true fi else log "dev thread ${THREAD_ROOT:0:20} has no issue mapping" - matrix_send "dev" "✓ received, will act on next poll" "$THREAD_ROOT" >/dev/null 2>&1 || true + matrix_send "dev" "❌ Could not inject: no issue mapping for this thread" "$THREAD_ROOT" >/dev/null 2>&1 || true fi # Only write to flat file when direct injection didn't happen, # to avoid supervisor/gardener poll re-injecting the same message. @@ -186,16 +205,18 @@ Consider this guidance for your current work." ;; review) # Route human questions to persistent review tmux session + # Thread map columns: 1=thread_id, 2=agent, 3=timestamp, 4=pr_num, 5=project REVIEW_PR_NUM=$(awk -F'\t' -v id="$THREAD_ROOT" '$1 == id {print $4}' "$THREAD_MAP" 2>/dev/null || true) + REVIEW_PROJECT=$(awk -F'\t' -v id="$THREAD_ROOT" '$1 == id {print $5}' "$THREAD_MAP" 2>/dev/null || true) if [ -n "$REVIEW_PR_NUM" ]; then - REVIEW_SESSION="review-${PROJECT_NAME}-${REVIEW_PR_NUM}" - REVIEW_PHASE_FILE="/tmp/review-session-${PROJECT_NAME}-${REVIEW_PR_NUM}.phase" + REVIEW_SESSION="review-${REVIEW_PROJECT}-${REVIEW_PR_NUM}" + REVIEW_PHASE_FILE="/tmp/review-session-${REVIEW_PROJECT}-${REVIEW_PR_NUM}.phase" if tmux has-session -t "$REVIEW_SESSION" 2>/dev/null; then # Skip injection if Claude is mid-review (phase file absent = actively writing) REVIEW_CUR_PHASE=$(head -1 "$REVIEW_PHASE_FILE" 2>/dev/null | tr -d '[:space:]' || true) if [ -z "$REVIEW_CUR_PHASE" ]; then - log "review session ${REVIEW_SESSION} is mid-review, deferring question" - matrix_send "review" "reviewer is busy — question queued, try again shortly" "$THREAD_ROOT" >/dev/null 2>&1 || true + log "WARN: review session '${REVIEW_SESSION}' is mid-review, deferring question for PR #${REVIEW_PR_NUM}" + matrix_send "review" "❌ Could not inject: reviewer is mid-review for PR #${REVIEW_PR_NUM}, try again shortly" "$THREAD_ROOT" >/dev/null 2>&1 || true else REVIEW_INJECT_MSG="Human question from ${SENDER} in Matrix: @@ -211,15 +232,19 @@ Please answer this question about your review. Explain your reasoning." tmux delete-buffer -b "review-q-${REVIEW_PR_NUM}" 2>/dev/null || true rm -f "$REVIEW_INJECT_TMP" log "review question from ${SENDER} injected into ${REVIEW_SESSION}" - matrix_send "review" "✓ question forwarded to reviewer session" "$THREAD_ROOT" >/dev/null 2>&1 || true + # Reply on first successful injection only + if ! grep -qF "$THREAD_ROOT" "$ACKED_FILE" 2>/dev/null; then + matrix_send "review" "✓ Question forwarded to reviewer session for PR #${REVIEW_PR_NUM}" "$THREAD_ROOT" >/dev/null 2>&1 || true + printf '%s\n' "$THREAD_ROOT" >> "$ACKED_FILE" + fi fi else - log "review session ${REVIEW_SESSION} not found for PR #${REVIEW_PR_NUM}" - matrix_send "review" "review session not active for PR #${REVIEW_PR_NUM}" "$THREAD_ROOT" >/dev/null 2>&1 || true + log "WARN: tmux session '${REVIEW_SESSION}' not found for PR #${REVIEW_PR_NUM} (project: ${REVIEW_PROJECT:-UNSET})" + matrix_send "review" "❌ Could not inject: tmux session '${REVIEW_SESSION}' not found (project: ${REVIEW_PROJECT:-UNSET})" "$THREAD_ROOT" >/dev/null 2>&1 || true fi else log "review thread ${THREAD_ROOT:0:20} has no PR mapping" - matrix_send "review" "review session not available" "$THREAD_ROOT" >/dev/null 2>&1 || true + matrix_send "review" "❌ Could not inject: no PR mapping for this thread" "$THREAD_ROOT" >/dev/null 2>&1 || true fi ;; action) @@ -242,14 +267,18 @@ Continue with the action formula based on this response." tmux delete-buffer -b "action-q-${ACTION_ISSUE}" 2>/dev/null || true rm -f "$ACTION_INJECT_TMP" log "human reply from ${SENDER} injected into ${ACTION_SESSION}" - matrix_send "action" "✓ reply forwarded to action session for issue #${ACTION_ISSUE}" "$THREAD_ROOT" >/dev/null 2>&1 || true + # Reply on first successful injection only + if ! grep -qF "$THREAD_ROOT" "$ACKED_FILE" 2>/dev/null; then + matrix_send "action" "✓ Reply forwarded to action session for issue #${ACTION_ISSUE}" "$THREAD_ROOT" >/dev/null 2>&1 || true + printf '%s\n' "$THREAD_ROOT" >> "$ACKED_FILE" + fi else - log "action session ${ACTION_SESSION} not found for issue #${ACTION_ISSUE}" - matrix_send "action" "action session not active for issue #${ACTION_ISSUE}" "$THREAD_ROOT" >/dev/null 2>&1 || true + log "WARN: tmux session '${ACTION_SESSION}' not found for issue #${ACTION_ISSUE}" + matrix_send "action" "❌ Could not inject: tmux session '${ACTION_SESSION}' not found" "$THREAD_ROOT" >/dev/null 2>&1 || true fi else log "action thread ${THREAD_ROOT:0:20} has no issue mapping" - matrix_send "action" "✓ received, no active session found" "$THREAD_ROOT" >/dev/null 2>&1 || true + matrix_send "action" "❌ Could not inject: no issue mapping for this thread" "$THREAD_ROOT" >/dev/null 2>&1 || true fi ;; vault) diff --git a/review/review-poll.sh b/review/review-poll.sh index 67877f4..c933aeb 100755 --- a/review/review-poll.sh +++ b/review/review-poll.sh @@ -54,7 +54,7 @@ if [ -n "$REVIEW_SESSIONS" ]; then rm -f "$phase_file" "/tmp/${PROJECT_NAME}-review-output-${pr_num}.json" \ "/tmp/review-injected-${PROJECT_NAME}-${pr_num}" # Prune thread-map entries for this PR - sed -i "/\t${pr_num}$/d" "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true + sed -i "/\treview\t[^\t]*\t${pr_num}\t/d" "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true cd "$REPO_ROOT" git worktree remove "/tmp/${PROJECT_NAME}-review-${pr_num}" --force 2>/dev/null || true rm -rf "/tmp/${PROJECT_NAME}-review-${pr_num}" 2>/dev/null || true @@ -70,7 +70,7 @@ if [ -n "$REVIEW_SESSIONS" ]; then rm -f "$phase_file" "/tmp/${PROJECT_NAME}-review-output-${pr_num}.json" \ "/tmp/review-injected-${PROJECT_NAME}-${pr_num}" # Prune thread-map entries for this PR - sed -i "/\t${pr_num}$/d" "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true + sed -i "/\treview\t[^\t]*\t${pr_num}\t/d" "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true cd "$REPO_ROOT" git worktree remove "/tmp/${PROJECT_NAME}-review-${pr_num}" --force 2>/dev/null || true rm -rf "/tmp/${PROJECT_NAME}-review-${pr_num}" 2>/dev/null || true @@ -84,7 +84,7 @@ if [ -n "$REVIEW_SESSIONS" ]; then tmux kill-session -t "$session" 2>/dev/null || true rm -f "$phase_file" "/tmp/${PROJECT_NAME}-review-output-${pr_num}.json" \ "/tmp/review-injected-${PROJECT_NAME}-${pr_num}" - sed -i "/\t${pr_num}$/d" "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true + sed -i "/\treview\t[^\t]*\t${pr_num}\t/d" "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true cd "$REPO_ROOT" git worktree remove "/tmp/${PROJECT_NAME}-review-${pr_num}" --force 2>/dev/null || true rm -rf "/tmp/${PROJECT_NAME}-review-${pr_num}" 2>/dev/null || true