fix(ci): pr-iphone-deploy no longer self-merges on its own comment
All checks were successful
CI / Detect Changes (pull_request) Successful in 3s
CI / YouTube Worker (pull_request) Has been skipped
CI / Deploy (pull_request) Has been skipped

The build-deploy job posts a 'Prêt à tester' comment whose body literally
contains 'Reply **LGTM** pour merger' and 'Reply **KO**' as instructions to
the reviewer. wait-approval re-fetches ALL comments every 30s and greps any
body for \bLGTM\b (checked before KO) — so on the first poll (~30s after
deploy) the bot matched its OWN comment and squash-merged automatically,
with no human review. Regressed in #6937a36 (PR #12) which tightened the
regex to catch 'Tested, LGTM!' but didn't notice the bot body matched.

Fix (sentinel marker + python parse, identity-agnostic so it's safe whether
PR_API_TOKEN is a bot or a personal account):

- build-deploy: embed <!-- tabatago:ready-to-test --> in the bot comment
  (HTML comment, invisible in rendered markdown, present in raw body).
- wait-approval: replace the fragile grep-over-JSON with a python3 heredoc
  using json.loads + the existing \bLGTM\b / \bKO\b regexes. Skip any
  comment whose body contains the sentinel marker. Check KO BEFORE LGTM so
  a PR with both signals blocks (matches the existing 'KO blocks' intent).
  LGTM still does {"Do":"squash"}; KO still blocks; 2h timeout unchanged.

Verified locally with three scenarios:
  bot-only       -> PENDING  (was LGTM before — the bug)
  bot + reviewer -> LGTM     (squash-merge still fires)
  bot + KO       -> KO       (block still fires, even with bot LGTM in history)

AGENTS.md §5 step 9, §5 pitfalls, and §8 anti-patterns updated so the doc
describes the marker rule and warns against re-introducing the self-match.
This commit is contained in:
Millian Lamiaux
2026-07-19 21:14:59 +02:00
parent 1ada238004
commit 98f4f82db2
2 changed files with 68 additions and 18 deletions

View File

@@ -172,10 +172,16 @@ jobs:
run: |
PR="${{ github.event.pull_request.number }}"
REPO="${{ github.repository }}"
# The HTML comment is invisible in Gitea's rendered markdown but is
# present in the raw body — wait-approval uses it as a sentinel to
# skip THIS bot comment when scanning for LGTM/KO (otherwise the
# instruction text "Reply LGTM pour merger" would self-trigger a
# merge ~30s after deploy). DO NOT remove or reword without also
# updating the scanner in the wait-approval job.
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (devicectl).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (devicectl).\\n\\n<!-- tabatago:ready-to-test -->\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR}/comments"
wait-approval:
@@ -202,6 +208,19 @@ jobs:
# Re-fetch ALL comments each cycle (not since_id) so we also catch
# edits — e.g. a reviewer changing "KO" → "LGTM". Negligible cost for
# PRs with <50 comments.
#
# Parsing is done with python3 (preinstalled on the macOS runner and
# already used by scripts/ci-status.py) rather than grep over raw JSON:
# - the bot's "Ready to test" comment body literally contains the
# words LGTM/KO as instructions to the reviewer, so any naive
# body grep would self-match and auto-merge ~30s after deploy.
# We skip it via the <!-- tabatago:ready-to-test --> sentinel
# embedded by the build-deploy job;
# - json.loads avoids false matches when the literal "body" key or
# trigger words appear inside another string field.
# KO is checked BEFORE LGTM so a PR with both signals blocks (matches
# the existing "KO blocks" intent — a reviewer who flip-flops shouldn't
# merge just because an older LGTM is still in the history).
while [ $TRIES -lt $MAX ]; do
sleep 30
@@ -210,23 +229,51 @@ jobs:
COMMENTS=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
"${API}/issues/${PR}/comments?limit=50&page=1")
# Match LGTM/KO anywhere in the body (with a word boundary so "KOM"
# or "LGTMX" don't trigger). Case-insensitive.
if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bLGTM\b'; then
echo "✅ LGTM reçu ! Squash-merge..."
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"squash\"}" \
"${API}/pulls/${PR}/merge"
echo "✅ Mergée (squash)."
exit 0
fi
DECISION=$(python3 - "$COMMENTS" <<'PY'
import json, re, sys
MARKER = "<!-- tabatago:ready-to-test -->"
try:
comments = json.loads(sys.argv[1] or "[]")
except Exception:
comments = []
lgtm = re.compile(r"\bLGTM\b", re.IGNORECASE)
ko = re.compile(r"\bKO\b", re.IGNORECASE)
hit_lgtm = hit_ko = False
for c in comments:
body = c.get("body") or ""
if MARKER in body:
# Skip the bot's own "Ready to test" comment — its body mentions
# LGTM/KO as instructions and would otherwise self-trigger.
continue
if ko.search(body):
hit_ko = True
if lgtm.search(body):
hit_lgtm = True
if hit_ko:
print("KO")
elif hit_lgtm:
print("LGTM")
else:
print("PENDING")
PY
)
if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bKO\b'; then
echo "❌ KO reçu. Bloquée."
exit 1
fi
case "$DECISION" in
LGTM)
echo "✅ LGTM reçu ! Squash-merge..."
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"squash\"}" \
"${API}/pulls/${PR}/merge"
echo "✅ Mergée (squash)."
exit 0
;;
KO)
echo "❌ KO reçu. Bloquée."
exit 1
;;
esac
if [ $((TRIES % 4)) -eq 0 ]; then
echo " ⏳ ... (${TRIES}/240, $(date +%H:%M))"