(ywc) Use when the user wants to merge Dependabot PRs, batch-process dependency updates, or clean up accumulated Dependabot PRs. Triggers: "merge dependabot", "dependabot PR", "dependency updates", "security updates merge", "디펜다봇 머지", "依存関係更新マージ". Do not use for non-Dependabot PRs, manual dependency upgrades, or for merging feature PRs (use ywc-create-pr or platform tools).
Installs into .claude/skills of the current project.
Are you the author of Ywc Merge Dependabot?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/yongwoon-ywc-merge-dependabot)
---
name: ywc-merge-dependabot
version: 1.0.0
description: (ywc) Use when the user wants to merge Dependabot PRs, batch-process dependency updates, or clean up accumulated Dependabot PRs. Triggers: "merge dependabot", "dependabot PR", "dependency updates", "security updates merge", "디펜다봇 머지", "依存関係更新マージ". Do not use for non-Dependabot PRs, manual dependency upgrades, or for merging feature PRs (use ywc-create-pr or platform tools).
category: release
phase: release
requires: []
advisor_budget: 0
allowed tools: Bash, Read
---
# Merge Dependabot Pull Requests
**Announce at start:** "I'm using the ywc-merge-dependabot skill to merge Dependabot PRs with CI verification — sequential by default, or parallel-auto (ecosystem-grouped queue) when requested."
Safely merge Dependabot PRs with CI verification, conflict resolution, and clear reporting.
## Rationalization Defense
When tempted to skip a step, check this table first:
| Excuse | Reality |
|---|---|
| "All ecosystems can run in parallel — fire all merges at once without grouping" | No. Within an ecosystem (e.g., multiple npm PRs all touching `package-lock.json`) merges still serialize via GitHub's auto-merge queue. parallel-auto only fans out across non-overlapping ecosystems, not within one. |
| "Mixed-ecosystem PR (touches both `package.json` and `pom.xml`) is fine to auto-merge" | No. Mixed PRs go to a final sequential pass; `--auto` cannot guarantee ordering when a single PR crosses two parallel groups. |
| "Just queue everything with `--auto` and walk away" | The polling loop and final summary are non-optional. Without them users have no audit trail of which PRs failed, why, or whether the queue stalled. |
| "Repo doesn't have auto-merge enabled, fall through silently" | If `parallel-auto` is requested but `gh repo view --json autoMergeAllowed` returns `false`, fall back to sequential mode and tell the user — never pretend the queue is running. |
| "CI is green but lockfile shows different hash, merge anyway" | Lockfile mismatch indicates dirty working state. Stop and rebase the PR first. |
| "Major version bump looks safe, just merge" | Major bumps need explicit user confirmation. Default action: skip and report for human review. |
| "Conflicts with main, force-push my own resolution" | Use Dependabot's `@dependabot rebase` comment instead. Hand-resolved conflicts in dep PRs hide breaking changes. |
| "Security mode, but PR has no CVE link" | If `security` mode is requested and CVE/advisory is missing, skip the PR and report it. |
| "All passed, no need for a final summary" | Always emit a per-PR result table (merged / skipped / failed) at the end. |
**Violating the letter of these rules is violating the spirit.** Dependabot velocity is worthless if a bad merge silently breaks main.
## Context
- Repository: !`gh repo view --json nameWithOwner --jq .nameWithOwner`
- Default branch: !`gh repo view --json defaultBranchRef --jq .defaultBranchRef.name`
## Mode Selection
This skill supports two orthogonal flags inside `$ARGUMENTS`. Parse the argument string as a set of space-separated tokens.
**Scope flag** — controls which PRs are eligible:
| Token | Scope |
| --- | --- |
| `security` | Security-related Dependabot PRs only |
| _(token absent)_ | All Dependabot PRs |
**Execution flag** — controls how eligible PRs are processed:
| Token | Execution | When to use |
| --- | --- | --- |
| `parallel-auto` | Group eligible PRs by lockfile ecosystem; queue every PR with `gh pr merge --auto`; GitHub serializes within each ecosystem and processes ecosystems in parallel; one final sequential pass clears the `mixed` bucket | Many PRs (≥ 5) across multiple ecosystems (npm + github-actions + python …) where wall-clock CI wait is the bottleneck |
| _(token absent)_ | Sequential — one PR at a time, ascending PR number | Small batches, repos with strict branch protection, or environments where auto-merge is disabled |
Examples:
- `(empty)` → all PRs, sequential (default)
- `security` → security PRs only, sequential
- `parallel-auto` → all PRs, ecosystem-grouped auto-merge
- `security parallel-auto` → security PRs only, ecosystem-grouped auto-merge
Security-related PRs are identified by labels like `security`, PR title containing "security", or Dependabot's security advisory metadata. When in doubt about whether a PR is security-related, check the PR body for CVE references or GitHub Security Advisory links.
**Parallel-auto prerequisite:** before entering parallel-auto mode, verify the repository allows auto-merge:
```bash
gh repo view --json autoMergeAllowed --jq .autoMergeAllowed
```
If the value is `false`, announce the fallback ("Auto-merge is not enabled on this repository — falling back to sequential mode") and proceed as if `parallel-auto` were absent. Never silently skip the queueing and pretend the work is in progress.
## Task
### 1. List Dependabot PRs
Retrieve all open PRs created by Dependabot:
```bash
gh pr list --author "app/dependabot" --state open --json number,title,labels,body --limit 100
```
If in **security-only mode**, filter to PRs that match any of:
- Have a `security` label
- Title or body contains "security advisory", "CVE-", "GHSA-", or "vulnerability"
- Created by `dependabot[bot]` with security update metadata in the body
Sort the resulting list by PR number ascending — processing order matters because earlier merges can affect later PRs.
### 2. Pre-Merge Checks
For each PR, run these checks **before** attempting to merge. If any check fails, skip the PR.
| Check | Skip condition | Skip label |
| --- | --- | --- |
| **Dockerfile FROM change** | PR modifies a `FROM` image version in any Dockerfile | `Skipped (Dockerfile)` |
| **Major version upgrade** | PR bumps a major version (e.g. 2.x → 3.x), which risks breaking changes | `Skipped (Major version upgrade)` |
| **CI status** | Required CI checks have not all passed | `Failed (CI not passing)` |
To detect a Dockerfile FROM change:
```bash
gh pr diff {number} --name-only | grep -qE '(^|/)(Dockerfile|Dockerfile\.[^/]*)$' && \
gh pr diff {number} | grep -qE '^[-+]FROM ' && \
echo "Dockerfile FROM change detected — skip"
```
To detect major version bumps, compare the version numbers in the PR title (Dependabot titles typically follow the pattern "Bump X from A to B"). A major bump means the leftmost non-zero version segment changed.
### 2.5. Ecosystem Grouping (parallel-auto mode only)
Skip this step in sequential mode. In parallel-auto mode, classify the eligible PR list by lockfile ecosystem using the bundled script. Resolve the script path relative to this skill folder (the directory containing this `SKILL.md`), not relative to the target repository.
```bash
python3 /path/to/ywc-merge-dependabot/scripts/group-by-ecosystem.py {pr_numbers...}
```
Replace `/path/to/ywc-merge-dependabot` with the actual skill directory path.
The script returns single-line JSON of the shape `{"groups": {ecosystem: [pr...]}, "errors": [...]}`. Recognised ecosystems: `npm`, `github-actions`, `python`, `go`, `cargo`, `maven`, `gradle`, `docker`. PRs touching no recognised marker, or markers from two or more ecosystems, land in the `mixed` group.
The grouping serves two purposes:
1. **Audit trail** — the final summary records which ecosystem each PR belonged to and which ones GitHub serialized vs parallelized.
2. **Mixed-bucket isolation** — `mixed` PRs are deliberately held back to a final sequential pass (Step 3b stage 3) so a single PR that crosses two parallel groups cannot race with itself.
Surface the grouping breakdown to the user before merging:
```
Ecosystem grouping (8 PRs eligible):
- npm: #201, #205, #207
- github-actions: #203, #209
- python: #204, #208
- mixed: #211
```
### 3. Merge Process
Choose the flow based on the execution flag from Mode Selection. Both flows preserve the same safety contract — only the merge invocation and waiting strategy differ.
#### 3a. Sequential Flow (default)
Process PRs one at a time in ascending PR number order. This is important because merging one PR can create conflicts in subsequent PRs.
For each PR that passes pre-merge checks:
```bash
# Check CI status
gh pr checks {number} --required
# Attempt merge
gh pr merge {number} --merge
```
**If a merge conflict occurs**, resolve it in this order. First, comment `@dependabot rebase` on the PR, then poll for the rebase to land (consistent with the Rationalization Defense above) — do not guess at how long to wait:
```bash
gh pr comment {number} --body "@dependabot rebase"
# Wait up to 10 minutes for Dependabot's rebase, polling every 30 seconds.
deadline=$(( $(date +%s) + 600 ))
lookup_failures=0
while [ "$(date +%s)" -lt "$deadline" ]; do
merge_status=$(gh pr view {number} --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null)
if [ -z "$merge_status" ]; then
lookup_failures=$((lookup_failures + 1))
sleep 30
continue
fi
lookup_failures=0
if [ "$merge_status" != "CONFLICTING" ] && [ "$merge_status" != "DIRTY" ] && [ "$merge_status" != "UNKNOWN" ]; then
echo "rebase landed, mergeStateStatus=$merge_status"
break
fi
sleep 30
done
```
A failed `gh pr view` lookup (empty output) is **not** evidence of a resolved conflict — it continues polling rather than exiting the loop. If lookups keep failing, treat it as a degraded/unknown state, not a safe merge signal.
If the loop exits at the deadline still `CONFLICTING`/`DIRTY`/`UNKNOWN`, or lookups kept failing, the rebase did **not** confirm-clear the conflict — fall back to the manual path.
> **Action required**: Read [`../references/pr-conflict-resolution.md`](../references/pr-conflict-resolution.md) and follow **Update Branch From Base** for the manual fallback — merge the base into the Dependabot branch (never rebase, it orphans review threads), push, and re-verify CI before merging. Do not inline or approximate that procedure here.
**If CI fails** after conflict resolution or at any point, skip the PR and record the failure.
After each successful merge, note it — the next PR in the queue may now have conflicts caused by this merge.
#### 3b. Parallel-Auto Flow
In parallel-auto mode the client offloads serialization to GitHub's auto-merge queue. The flow has three stages and the client never blocks on per-PR CI manually — GitHub handles rebase and CI orchestration.
**Stage 1 — Queue every non-mixed PR for auto-merge:**
For each ecosystem group's PRs (the order across groups does not matter — they are non-conflicting by construction), queue them all at once:
```bash
gh pr merge {number} --auto --merge
```
This sets the PR to merge automatically as soon as its CI is green and the branch is up to date. PRs sharing a lockfile (same ecosystem) automatically serialize because each merge invalidates the next PR's branch state, and Dependabot rebases the affected PRs; PRs in different ecosystems progress in parallel because GitHub processes them independently.
**Stage 2 — Poll the queue until it drains:**
Track each queued PR's state until it reaches a terminal state. Use a single bounded loop (do not spin in tight retries):
```bash
# Wait up to 30 minutes total, polling every 60 seconds.
deadline=$(( $(date +%s) + 1800 ))
remaining=(101 105 107 203 209 204 208) # queued PR numbers
declare -A status
while [ ${#remaining[@]} -gt 0 ] && [ "$(date +%s)" -lt "$deadline" ]; do
next=()
for pr in "${remaining[@]}"; do
pr_state=$(gh pr view "$pr" --json state --jq '.state' 2>/dev/null || echo UNKNOWN)
merge_status=$(gh pr view "$pr" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null || echo UNKNOWN)
if [ "$pr_state" = "MERGED" ]; then
status[$pr]="merged"
elif [ "$pr_state" = "CLOSED" ]; then
status[$pr]="closed"
elif [ "$merge_status" = "CONFLICTING" ] || [ "$merge_status" = "DIRTY" ]; then
# Stay in queue — Dependabot or a maintainer may rebase before the deadline.
next+=("$pr")
else
next+=("$pr")
fi
done
remaining=("${next[@]}")
[ ${#remaining[@]} -gt 0 ] && sleep 60
done
# Any PR still in `remaining` after the loop is recorded as queue-stalled.
for pr in "${remaining[@]}"; do
status[$pr]="stalled"
done
```
PRs that reach `MERGED` are recorded as `Merged`. PRs still in the `remaining` list after the deadline are recorded as `Failed (queue stalled)` so the user can investigate. Do not extend the deadline silently — if the queue genuinely needs more time, the user can rerun the skill.
**Stage 3 — Final sequential pass for the mixed bucket:**
After the parallel queue has drained (Stage 2 returned), process the `mixed` group using the same logic as Sequential Flow (3a). Mixed PRs run last because earlier merges may have changed the conflict surface they would land in.
### 4. Final Summary
After processing all PRs, report results in this format:
```
## Dependabot Merge Results
Mode: parallel-auto (security)
Ecosystem groups processed: npm (3), github-actions (2), python (2), mixed (1)
- ✅ Merged (npm) : #123 Bump axios from 1.6.0 to 1.7.2
- ✅ Merged (npm) : #125 Bump lodash from 4.17.20 to 4.17.21
- ✅ Merged (github-actions) : #129 Bump actions/checkout from 4.1.1 to 4.2.0
- ⏭️ Skipped (Dockerfile) : #127 Bump node from 18 to 20
- ⏭️ Skipped (Major version) : #130 Bump webpack from 4.46.0 to 5.90.0
- ❌ Failed (queue stalled) : #132 Bump express from 4.18.0 to 4.19.2 — CONFLICTING after 30 min
- ❌ Failed (mixed pass) : #135 Bump aws-sdk + boto3 — manual conflict resolution required
Total: 3 merged / 2 skipped / 2 failed
```
Include:
- Mode line — scope flag + execution flag, so the reader can reproduce the run
- Ecosystem group summary — only when parallel-auto was used; the count tells the reader how many groups GitHub serialized in parallel
- Per-PR result — for parallel-auto, annotate each line with the ecosystem; for sequential, omit the annotation
- If a previous merge affected a subsequent PR, note which PR caused the issue
- Total counts: merged / skipped / failed
## Error Handling
| Error | Action |
| --- | --- |
| `gh` CLI not authenticated | Stop and ask user to run `gh auth login` |
| No Dependabot PRs found | Report "No Dependabot PRs found" and stop |
| Rate limit hit | Wait and retry, or stop and report progress so far |
| Branch protection prevents merge | Skip PR and note the protection rule in the report |
## Notes
- Follow any additional instructions in `$ARGUMENTS`
- Never force-merge or bypass branch protection rules
- If the number of PRs is large (>20), ask the user for confirmation before proceeding. For batches of that size, also recommend `parallel-auto` mode if the eligible PRs span more than one ecosystem
- `parallel-auto` reduces wall-clock time by letting GitHub run the CI cycle for non-conflicting groups concurrently. It does not reduce the *per-PR* CI cost, the safety surface, or the conflict-resolution requirements — it only changes how the queue is orchestrated
## Integration
- **upstream**: None — this skill is triggered directly by the user or a scheduled workflow when Dependabot PRs accumulate
- **downstream**: /ywc-gen-testcase (optional — run after merging dependency updates to verify the updated packages do not break existing test coverage)