From d023c997535a0c32cac9d3dea54f3de699d0dd0c Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Fri, 27 Mar 2026 02:44:29 -0600 Subject: [PATCH 1/3] fix(skill): block @greptileai trigger until all comments replied Prevents the review skill from posting @greptileai re-triggers before replying to every inline Greptile comment. Adds a Step 2g verification script that checks for unanswered comments and blocks the trigger if any exist. Updates the Rules section to make reply-before-trigger the explicit invariant. --- .claude/skills/review/SKILL.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.claude/skills/review/SKILL.md b/.claude/skills/review/SKILL.md index 541bc14b..c6a1b4f4 100644 --- a/.claude/skills/review/SKILL.md +++ b/.claude/skills/review/SKILL.md @@ -203,6 +203,32 @@ After addressing all comments for a PR: **Greptile:** Always re-trigger after replying to Greptile comments — whether the comment was actionable or not. The **only** exception is if Greptile already reacted to your most recent reply with a positive emoji (thumbs up, check, etc.), which means it is already satisfied. +**CRITICAL — verify all Greptile comments have replies BEFORE triggering.** Posting `@greptileai` without replying to every comment is worse than not triggering at all — it starts a new review cycle while the old one still has unanswered feedback. Run this check first: + +```bash +# Step 0: Verify every Greptile inline comment has at least one reply from us +greptile_comment_ids=$(gh api repos/optave/codegraph/pulls//comments --paginate \ + --jq '[.[] | select(.user.login == "greptile-apps[bot]")] | .[].id') + +unanswered=() +for cid in $greptile_comment_ids; do + reply_count=$(gh api repos/optave/codegraph/pulls//comments --paginate \ + --jq "[.[] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") + if [ "$reply_count" -eq 0 ]; then + unanswered+=("$cid") + fi +done + +if [ ${#unanswered[@]} -gt 0 ]; then + echo "BLOCKED — ${#unanswered[@]} Greptile comments have no reply: ${unanswered[*]}" + echo "Go back to Step 2e and reply to each one before re-triggering." + exit 1 +fi +echo "All Greptile comments have replies — safe to re-trigger." +``` + +**Do NOT proceed to the re-trigger step below until the check above passes.** If any comments are unanswered, go back to Step 2e, reply to each one, then re-run this check. + ```bash # Step 1: Check if greptileai left a positive reaction on your most recent reply last_reply_id=$(gh api repos/optave/codegraph/issues//comments --paginate \ @@ -276,7 +302,7 @@ If any subagent failed or returned an error, note it in the Status column as `ag - **Never force-push** unless fixing a commit message that fails commitlint. Amend + force-push is the only way to fix a pushed commit title (messages are part of the SHA). This is safe on feature branches. For all other problems, fix with a new commit. - **Address ALL comments from ALL reviewers** (Claude, Greptile, and humans), even minor/nit/optional ones. Leave zero unaddressed. Do not only respond to one reviewer and skip another. - **Always reply to comments** explaining what was done. Don't just fix silently. Every reviewer must see a reply on their feedback. -- **Always re-trigger Greptile after replying.** Every time you reply to a Greptile comment — actionable or not — post `@greptileai` to re-trigger a review. The only exception: if Greptile already reacted to your reply with a positive emoji (thumbs up), skip the re-trigger. +- **Never trigger `@greptileai` without replying to every Greptile comment first.** Before posting the re-trigger, run the Step 2g verification script to confirm zero unanswered Greptile comments. Triggering a new review while old comments are unanswered is a blocking violation — it creates review noise and signals that feedback was ignored. Reply first, verify, then trigger. - **Only re-trigger Claude** if you addressed Claude's feedback specifically. - **No co-author lines** in commit messages. - **No Claude Code references** in commit messages or comments. From 5cf43f699d18f20d1211368b9dd35f17c25a4bd8 Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Fri, 27 Mar 2026 04:17:50 -0600 Subject: [PATCH 2/3] fix(skill): filter root Greptile comments and batch API calls (#656) --- .claude/skills/review/SKILL.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.claude/skills/review/SKILL.md b/.claude/skills/review/SKILL.md index c6a1b4f4..cfc6aebe 100644 --- a/.claude/skills/review/SKILL.md +++ b/.claude/skills/review/SKILL.md @@ -207,13 +207,15 @@ After addressing all comments for a PR: ```bash # Step 0: Verify every Greptile inline comment has at least one reply from us -greptile_comment_ids=$(gh api repos/optave/codegraph/pulls//comments --paginate \ - --jq '[.[] | select(.user.login == "greptile-apps[bot]")] | .[].id') +all_comments=$(gh api repos/optave/codegraph/pulls//comments --paginate) + +greptile_comment_ids=$(echo "$all_comments" \ + | jq -r '[.[] | select(.user.login == "greptile-apps[bot]" and .in_reply_to_id == null)] | .[].id') unanswered=() for cid in $greptile_comment_ids; do - reply_count=$(gh api repos/optave/codegraph/pulls//comments --paginate \ - --jq "[.[] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") + reply_count=$(echo "$all_comments" \ + | jq "[.[] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") if [ "$reply_count" -eq 0 ]; then unanswered+=("$cid") fi From fa62a4afba34837b458e80772eb02ea40de4c4be Mon Sep 17 00:00:00 2001 From: carlos-alm <127798846+carlos-alm@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:33:36 -0600 Subject: [PATCH 3/3] fix(skill): use jq -s to handle multi-page paginated responses (#656) gh api --paginate emits one JSON array per page. Without -s, jq produces one number per page, causing the integer comparison to fail silently and bypass the blocking check. Using jq -s slurps all pages into a single array. Also rewords the "only exception" language on the Greptile re-trigger paragraph so it no longer conflicts with the mandatory all-replies pre-condition added earlier. --- .claude/skills/review/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/review/SKILL.md b/.claude/skills/review/SKILL.md index cfc6aebe..bcdb56b3 100644 --- a/.claude/skills/review/SKILL.md +++ b/.claude/skills/review/SKILL.md @@ -201,7 +201,7 @@ After addressing all comments for a PR: ### 2g. Re-trigger reviewers -**Greptile:** Always re-trigger after replying to Greptile comments — whether the comment was actionable or not. The **only** exception is if Greptile already reacted to your most recent reply with a positive emoji (thumbs up, check, etc.), which means it is already satisfied. +**Greptile:** Always re-trigger after replying to Greptile comments — whether the comment was actionable or not. First, run the verification script below to confirm all Greptile comments have replies. Then, skip the actual trigger only if Greptile already reacted to your most recent reply with a positive emoji (thumbs up, check, etc.), which means it is already satisfied. **CRITICAL — verify all Greptile comments have replies BEFORE triggering.** Posting `@greptileai` without replying to every comment is worse than not triggering at all — it starts a new review cycle while the old one still has unanswered feedback. Run this check first: @@ -215,7 +215,7 @@ greptile_comment_ids=$(echo "$all_comments" \ unanswered=() for cid in $greptile_comment_ids; do reply_count=$(echo "$all_comments" \ - | jq "[.[] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") + | jq -s "[.[][] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") if [ "$reply_count" -eq 0 ]; then unanswered+=("$cid") fi