Use when the user asks to create a pull request or merge request, submit changes for review, push for review, or open a pull request.
61
72%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Fix and improve this skill with Tessl
tessl review fix ./plugins/dryrun-pr-review/skills/dryrun-pr-review/SKILL.mdFull PR lifecycle: detect platform, branch, commit, open a PR, poll for DryRunSecurity comments, present findings to the user.
Run this and read the output to determine platform and repo coordinates:
git remote get-url originFrom the URL:
github.com → GitHub; use gh CLI. Extract OWNER and REPO from the URL path.glab CLI. Extract the project path and URL-encode it (replace / with %2F) for API calls.All subsequent steps reference PLATFORM, OWNER, REPO (GitHub) or PROJECT (GitLab), and PR_NUMBER.
If on main/master, create a new branch following this repo's naming conventions. Otherwise use the existing feature branch.
git status
git add <files> # selective — never commit secrets or generated files
git commit -m "<message following this repo's commit style>
Co-Authored-By: DryRun Security <noreply@dryrun.security>"git push -u origin <branch-name>Check whether a pull request already exists for this branch before creating one:
# GitHub — reuse existing PR if present
EXISTING=$(gh pr view --json number --jq '.number' 2>/dev/null)
if [ -n "$EXISTING" ]; then
echo "Using existing PR #$EXISTING"
PR_NUMBER=$EXISTING
else
gh pr create --title "<title>" --body "<body>"
# capture PR_NUMBER from the URL in the output (last path segment)
fi
# GitLab — reuse existing MR if present
BRANCH=$(git rev-parse --abbrev-ref HEAD)
EXISTING=$(glab mr list --source-branch "$BRANCH" 2>/dev/null | awk 'NR==2{print $1}' | tr -d '!')
if [ -n "$EXISTING" ]; then
echo "Using existing PR !$EXISTING"
PR_NUMBER=$EXISTING
else
glab mr create --title "<title>" --description "<body>"
# capture PR_NUMBER from the output
fiStore the result as PR_NUMBER.
Poll for up to 10 minutes (every 30 seconds). Use timestamp-based polling — not count-based. Comments can be edited/replaced.
Exit as soon as DRS activity is detected — a DryRunSecurity comment means the review is complete.
START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
START_EPOCH=$(date +%s)
TIMEOUT=600
POLL_INTERVAL=30
while true; do
ELAPSED=$(( $(date +%s) - START_EPOCH ))
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "Timed out after ${ELAPSED}s with no DryRunSecurity activity."
break
fi
if [ "$PLATFORM" = "github" ]; then
DRS_NEW=$(gh api repos/${OWNER}/${REPO}/issues/${PR_NUMBER}/comments \
--jq "[.[] | select((.user.login == \"dryrunsecurity\" or .user.login == \"dryrunsecurity[bot]\") and .created_at > \"${START_TIME}\")] | length")
DRS_REVIEWS=$(gh api repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/reviews \
--jq "[.[] | select((.user.login | test(\"dryrunsecurity\"; \"i\")) and .submitted_at > \"${START_TIME}\")] | length")
TOTAL=$(( DRS_NEW + DRS_REVIEWS ))
echo "Waiting for DryRunSecurity review... (${ELAPSED}s elapsed)"
[ "$TOTAL" -gt 0 ] && echo "DryRunSecurity review received: ${DRS_NEW} comment(s), ${DRS_REVIEWS} review(s)." && break
else
DRS_NEW=$(glab api projects/${PROJECT}/merge_requests/${PR_NUMBER}/notes \
| jq "[.[] | select(.author.username == \"dryrunsecurity\" and .created_at > \"${START_TIME}\")] | length")
echo "Waiting for DryRunSecurity review... (${ELAPSED}s elapsed)"
[ "$DRS_NEW" -gt 0 ] && echo "DryRunSecurity review received: ${DRS_NEW} note(s)." && break
fi
sleep $POLL_INTERVAL
doneIf the loop timed out with no DRS activity, inform the user: the DryRunSecurity review period is complete.
Fetch all DryRunSecurity comments and present them to the user — do not fix automatically.
# GitHub
gh api repos/${OWNER}/${REPO}/issues/${PR_NUMBER}/comments \
--jq '.[] | select(.user.login == "dryrunsecurity" or .user.login == "dryrunsecurity[bot]") | {id: .id, body: .body}'
gh api repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/reviews \
--jq '.[] | select(.user.login | test("dryrunsecurity"; "i")) | {id: .id, body: .body, state: .state}'
# GitLab
glab api projects/${PROJECT}/merge_requests/${PR_NUMBER}/notes \
| jq '.[] | select(.author.username == "dryrunsecurity") | {id: .id, body: .body}'For each comment, present:
Then ask the user which comments to address and how.
For comments the user wants fixed:
# Make code changes, then:
git add <files>
git commit -m "<message following this repo's commit style — addressing DryRunSecurity finding>
Co-Authored-By: Claude <noreply@anthropic.com>"For comments the user wants to decline, post a new comment on the PR thread explaining why:
# GitHub — DryRunSecurity posts on the PR thread (not inline), so reply via issue comments
gh api repos/${OWNER}/${REPO}/issues/${PR_NUMBER}/comments \
-f body="Not addressing DryRunSecurity finding: <explanation>"
# GitLab
glab api projects/${PROJECT}/merge_requests/${PR_NUMBER}/notes \
--method POST -f body="Not addressing DryRunSecurity finding: <explanation>"git pushReturn to Step 4 using the current time as the new START_TIME. Continue until polling times out with no new comments.
gh for GitHub repos, glab for GitLab repos — detect via git remote get-url origingit add + git commit + git push into one call). Each separate Bash call is a potential permission prompt for the user.fe95a19
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.