fix(skills): prevent /review from spamming @greptileai when already approved#628
fix(skills): prevent /review from spamming @greptileai when already approved#628carlos-alm wants to merge 3 commits intomainfrom
Conversation
…pproved The review skill's re-trigger logic only checked for emoji reactions on reply comments, missing the case where Greptile's initial review was already positive with no inline comments (e.g. thumbsup + no comments). This caused repeated @greptileai triggers on PRs like #621. Replace the single emoji-on-reply check with a proper decision matrix: - Thumbsup on trigger + no inline comments + >15min = approved, skip - <15min since trigger = wait, don't re-trigger - Outstanding inline comments after addressing = re-trigger
|
Claude finished @carlos-alm's task —— View job PR Review AnalysisReviewing PR #628: fix(skills): prevent /review from spamming @greptileai when already approved Todo List
Root Cause AnalysisProblem Being Solved: The Why This Approach: Replaces a simplistic "check reactions on your last reply" method with a sophisticated decision matrix that:
Risk Assessment: LOW RISK. This is a pure logic improvement to an automation workflow with no impact on core functionality. Backlog Compliance
Critical ConcernsNone identified. The implementation is well-structured and addresses the root cause properly. Technical Analysis✅ Strengths:
✅ Code Quality:
✅ No Config/Test Integrity Issues:
🔍 Minor Observations:
Final Recommendation
The decision matrix properly handles all the edge cases mentioned in the PR description and should eliminate the spam behavior while maintaining proper reviewer workflow. |
Greptile SummaryThis PR hardens the Confidence Score: 4/5Safe to merge with one low-risk edge case to address: Check 2 may suppress the first-ever @greptileai trigger on a fresh PR. All four prior review-thread concerns are correctly resolved (null-trigger guard, bot-comment exclusion, missing decision branches, elapsed-time tooling). The bash snippets and jq filters are syntactically correct and the decision matrix is now exhaustive. One remaining P2 ambiguity exists — the first-ever trigger case routing through Check 2 — but it only affects PRs where @greptileai was never previously posted, and the overall logic is sound. Rewarding convergence: prior concerns fully addressed. .claude/skills/review/SKILL.md lines 213–247 (first-run trigger_count=0 path and Check 2 interaction) Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Step 2g: Re-trigger Greptile?] --> B{trigger_count == 0?}
B -- Yes --> C[No prior trigger\nProceed to Check 2]
B -- No --> D[Get last trigger comment\nid + created_at]
D --> E[Fetch thumbsup_count\nfetch inline_count\ncompute elapsed_minutes]
E --> F{thumbsup exists?}
F -- Yes --> G{new inline comments?}
G -- Yes --> H[Address inline comments\nthen re-trigger]
G -- No --> I{elapsed > 15 min?}
I -- Yes --> J[Greptile approved\nSkip re-triggering]
I -- No --> K[Still processing\nWait, then re-check]
F -- No --> L{elapsed > 15 min?}
L -- No --> M[Not responded yet\nWait, then re-check]
L -- Yes --> N{new inline comments?}
N -- Yes --> O[Address inline comments\nthen re-trigger]
N -- No --> P[Never responded\nRe-trigger once]
C --> Q{Check 2: any code changes\nin response to Greptile?}
H --> Q
O --> Q
P --> Q
Q -- No --> R[Skip re-triggering]
Q -- Yes --> S[Post @greptileai]
Reviews (2): Last reviewed commit: "fix(skills): address Greptile review fee..." | Re-trigger Greptile |
| **Decision logic:** | ||
| - Thumbsup exists AND no new inline comments AND >15 min since trigger → **Greptile approved. Skip re-triggering.** | ||
| - Thumbsup exists AND no new inline comments AND <15 min since trigger → **Greptile is still processing. Wait until 15 min have passed**, then re-check. Do NOT post another `@greptileai`. | ||
| - No thumbsup AND <15 min since trigger → **Greptile hasn't responded yet. Wait until 15 min have passed**, then re-check. | ||
| - No thumbsup AND >15 min AND new inline comments exist → **Greptile has concerns. Address them (step 2e), then re-trigger.** |
There was a problem hiding this comment.
Decision matrix has two unhandled cases
The four listed cases leave gaps that will leave the agent with no instruction:
-
No thumbsup AND >15 min AND no new inline comments— Greptile never responded at all (no reaction, no inline comments) after the full wait. The matrix says nothing about this state. The agent will likely stall or make an arbitrary choice. The correct behavior here is probably to re-trigger (Greptile may have missed the notification). -
Thumbsup exists AND new inline comments exist(regardless of time) — Greptile approved but also left inline comments. This is a contradictory-but-real state (e.g., Greptile leaves nit comments but still thumbsups). The matrix doesn't say whether to treat the thumbsup or the inline comments as the authoritative signal.
Suggested additions to the decision logic:
- No thumbsup AND >15 min AND no new inline comments → **Greptile never responded. Re-trigger once** (it may have missed the notification).
- Thumbsup exists AND new inline comments exist → **Treat inline comments as outstanding concerns. Address them (step 2e), then re-trigger.**There was a problem hiding this comment.
Fixed. Added both missing branches to the decision matrix:
- Thumbsup + inline comments → Treat inline comments as outstanding concerns. Address them, then re-trigger. (Greptile sometimes thumbsups but still leaves nit comments.)
- No thumbsup + >15 min + no inline comments → Greptile never responded. Re-trigger once (it may have missed the notification).
.claude/skills/review/SKILL.md
Outdated
| # Find the last @greptileai trigger comment and its reactions | ||
| trigger_comment=$(gh api repos/optave/codegraph/issues/<number>/comments --paginate \ | ||
| --jq '[.[] | select(.body | test("@greptileai"))] | last | {id: .id, created_at: .created_at}') | ||
| trigger_id=$(echo "$trigger_comment" | jq -r '.id') | ||
| trigger_time=$(echo "$trigger_comment" | jq -r '.created_at') |
There was a problem hiding this comment.
No guard when no prior
@greptileai trigger exists
[...] | last on an empty array returns null in jq. If a PR has never had an @greptileai comment (first run), trigger_id and trigger_time are both the string "null", causing the subsequent reactions API call to hit /issues/comments/null/reactions (a 404) and the inline-comments filter to compare timestamps against the literal string "null".
The agent should detect the empty case upfront and skip the matrix entirely (jumping straight to check 2 / re-trigger):
trigger_count=$(gh api repos/optave/codegraph/issues/<number>/comments --paginate \
--jq '[.[] | select(.body | test("@greptileai"))] | length')
if [ "$trigger_count" -eq 0 ]; then
# No prior trigger — skip approval check, proceed to check 2
...
fiThere was a problem hiding this comment.
Fixed. Added a \ check upfront — if no prior \ trigger exists (excluding Greptile's own responses), the script skips the approval check entirely and proceeds to check 2 (whether we addressed any Greptile feedback). This avoids the null ID/timestamp issue.
.claude/skills/review/SKILL.md
Outdated
| # Find the last @greptileai trigger comment and its reactions | ||
| trigger_comment=$(gh api repos/optave/codegraph/issues/<number>/comments --paginate \ | ||
| --jq '[.[] | select(.body | test("@greptileai"))] | last | {id: .id, created_at: .created_at}') | ||
| trigger_id=$(echo "$trigger_comment" | jq -r '.id') | ||
| trigger_time=$(echo "$trigger_comment" | jq -r '.created_at') | ||
|
|
||
| # Check if greptile-apps[bot] thumbsupped the trigger comment | ||
| gh api repos/optave/codegraph/issues/comments/$trigger_id/reactions \ | ||
| --jq '[.[] | select(.user.login == "greptile-apps[bot]" and .content == "+1")] | length' | ||
|
|
||
| # Count Greptile inline comments created after the trigger | ||
| gh api repos/optave/codegraph/pulls/<number>/comments --paginate \ | ||
| --jq "[.[] | select(.user.login == \"greptile-apps[bot]\" and .created_at > \"$trigger_time\")] | length" |
There was a problem hiding this comment.
test("@greptileai") will match Greptile's own response comments
Greptile's review responses frequently contain the phrase @greptileai in their body (e.g. "I reviewed this as requested via @greptileai"). The select(.body | test("@greptileai")) filter picks up those Greptile-authored comments too, so | last may resolve to a Greptile response rather than the human trigger comment. This would cause the reactions and inline-comment checks to be run against the wrong comment ID/timestamp.
The filter should exclude Greptile's own comments:
trigger_comment=$(gh api repos/optave/codegraph/issues/<number>/comments --paginate \
--jq '[.[] | select(.body | test("@greptileai")) | select(.user.login != "greptile-apps[bot]")] | last | {id: .id, created_at: .created_at}')There was a problem hiding this comment.
Fixed. The jq filter now excludes Greptile's own comments with select(.user.login != "greptile-apps[bot]") in both the count query and the last-trigger query, so | last will only resolve to an actual human trigger comment.
| **Decision logic:** | ||
| - Thumbsup exists AND no new inline comments AND >15 min since trigger → **Greptile approved. Skip re-triggering.** | ||
| - Thumbsup exists AND no new inline comments AND <15 min since trigger → **Greptile is still processing. Wait until 15 min have passed**, then re-check. Do NOT post another `@greptileai`. | ||
| - No thumbsup AND <15 min since trigger → **Greptile hasn't responded yet. Wait until 15 min have passed**, then re-check. | ||
| - No thumbsup AND >15 min AND new inline comments exist → **Greptile has concerns. Address them (step 2e), then re-trigger.** |
There was a problem hiding this comment.
No tooling provided for the elapsed-time check
The decision branches on >15 min vs <15 min since the trigger, but the bash block gives the agent no command to compute elapsed time. The agent receives $trigger_time (an ISO 8601 string) and must infer "now" on its own — which is reliable only if the agent's clock/date context is accurate.
A concrete command removes ambiguity:
# Elapsed time in seconds since the trigger
elapsed_seconds=$(( $(date -u +%s) - $(date -u -d "$trigger_time" +%s 2>/dev/null || date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$trigger_time" +%s) ))
elapsed_minutes=$(( elapsed_seconds / 60 ))
echo "Minutes since trigger: $elapsed_minutes"(The date -d form works on Linux; the -j -f form works on macOS — providing both or using python3 -c makes it portable.)
There was a problem hiding this comment.
Fixed. Added concrete elapsed-time computation using python3 for ISO 8601 parsing (portable across platforms) and date -u +%s for current epoch. The result is stored in $elapsed_minutes which the decision logic references directly.
- Exclude greptile-apps[bot] from trigger comment filter to prevent matching Greptile's own response bodies containing @greptileai - Add null guard: check trigger_count before querying reactions API to avoid 404 when no prior @greptileai comment exists - Add two missing decision matrix branches: "thumbsup + inline comments" and "no thumbsup + >15 min + no inline comments" - Add elapsed-time computation using python3 + date for reliable minute calculation
Summary
/reviewskill blindly re-triggering@greptileaieven when Greptile already approved (thumbsup + no inline comments), as seen on PR refactor: address SLOC warnings in domain and features layers #621Test plan
/reviewon a PR where Greptile already gave thumbsup + no comments — verify it skips re-triggering/reviewon a PR with outstanding Greptile inline comments — verify it addresses them and re-triggers/reviewon a PR where@greptileaiwas just posted (<15 min ago) — verify it waits instead of posting again