From eeae1fdaaa72769db130d76b60d7f1433eef5034 Mon Sep 17 00:00:00 2001 From: ffaerber Date: Wed, 8 Jul 2026 09:35:19 +0200 Subject: [PATCH] fix: deterministic dedup of agent double-posts (self-post + framework reply) (#109) --- .gitea/workflows/scripts/fetch-thread.sh | 6 ++++++ .gitea/workflows/scripts/publish.sh | 26 +++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/scripts/fetch-thread.sh b/.gitea/workflows/scripts/fetch-thread.sh index 0956bb3..e1adda6 100755 --- a/.gitea/workflows/scripts/fetch-thread.sh +++ b/.gitea/workflows/scripts/fetch-thread.sh @@ -26,3 +26,9 @@ jq -rs ' "### comment by \($who):\n\(.body | gsub("\\s*"; ""))\n"' \ /tmp/thread_pages.json > /tmp/thread.md 2>/dev/null || : > /tmp/thread.md echo "thread comments fetched: $(grep -c '^### comment by ' /tmp/thread.md 2>/dev/null || echo 0) (newest 100 kept)" + +# Record the newest comment id on the thread BEFORE the agent runs. publish.sh compares against +# it to detect an agent that self-posted its reply mid-run (via the gitea-api skill, despite the +# prompt telling it not to) and skips the duplicate framework reply. Ids are monotonic — no dates. +jq -rs '[ (add // [])[].id ] | max // 0' /tmp/thread_pages.json > /tmp/thread_max_cid 2>/dev/null || echo 0 > /tmp/thread_max_cid +echo "pre-run newest comment id: $(cat /tmp/thread_max_cid)" diff --git a/.gitea/workflows/scripts/publish.sh b/.gitea/workflows/scripts/publish.sh index 8effabe..2422a4f 100755 --- a/.gitea/workflows/scripts/publish.sh +++ b/.gitea/workflows/scripts/publish.sh @@ -195,7 +195,31 @@ if [ "$MODE" != "pr" ]; then done < /tmp/subtasks.txt subtext=$(printf '\n\n---\nCreated sub-issues%s (mention an agent on each when ready):%b' "${ms:+ under milestone **$ms**}" "$links") fi - post "$(printf '%s%s%s' "$msg" "$subtext" "$activity")" + # DEDUP GUARD (issue: @pm double-posts its report). Prompt-level "do not self-post" is ignored + # by some models, so enforce it here: if the agent ALREADY posted a comment on this thread + # during the run (any comment by $NAME newer than the pre-run newest id from fetch-thread.sh), + # its self-post IS the reply — skip the duplicate framework comment. Markers (CLOSE_ISSUE, + # DELEGATE, MERGE_PR, subtasks) were already processed above and are unaffected. + # FAIL OPEN: if the pre-run marker is missing (fetch-thread hiccup), pre_cid=0 would make the + # agent's comments from PREVIOUS runs count as self-posts and wrongly suppress the reply. + # Without the marker, skip the guard and post normally. + pre_cid=$(cat /tmp/thread_max_cid 2>/dev/null || echo "") + selfposts=0 + if [ -n "$pre_cid" ]; then + : > /tmp/all_comments.json + for pg in $(seq 1 10); do + cpg=$(curl -sS "${hdr[@]}" "$API/issues/$NUM/comments?limit=50&page=$pg" 2>/dev/null) || cpg='[]' + cn=$(printf '%s' "$cpg" | jq 'if type=="array" then length else 0 end' 2>/dev/null || echo 0) + [ "${cn:-0}" -gt 0 ] && printf '%s\n' "$cpg" >> /tmp/all_comments.json + [ "${cn:-0}" -lt 50 ] && break + done + selfposts=$(jq -rs --arg n "$NAME" --argjson c "${pre_cid:-0}" '[ (add // [])[] | select(.user.login==$n) | select(.id > $c) ] | length' /tmp/all_comments.json 2>/dev/null || echo 0) + fi + if [ "${selfposts:-0}" -gt 0 ]; then + echo "agent @$NAME already posted ${selfposts} comment(s) on #$NUM during this run — skipping duplicate framework reply" + else + post "$(printf '%s%s%s' "$msg" "$subtext" "$activity")" + fi # --- @pm autopilot merge: @pm is the ONLY agent that merges, and ONLY under the autopilot label --- # (@qa never merges — it approves and hands back here.) Merge with the PAT (TTOK), not the built-in