merging-dependabot-prs
apache/struts/.claude/skills/merging-dependabot-prs/SKILL.md
Use when triaging, classifying or landing Dependabot pull requests in this repo — clearing the open Dependabot queue, deciding whether a bump needs a WW Jira ticket, or checking whether a bump's build actually passed.
Skill1.4k starsChanged yesterday
What's in it
- Merging Dependabot PRs
- Overview
- The decision
- Finding the real scope
- Is the build actually green?
- Checkpoint — classify, then stop
- Landing a bare bump
- Landing a ticketed bump
- One dependency, two branches, one ticket
- Scope of this skill
- Traps
- Red flags — stop and re-read
- Related
---
name: merging-dependabot-prs
description: Use when triaging, classifying or landing Dependabot pull requests in this repo — clearing the open Dependabot queue, deciding whether a bump needs a WW Jira ticket, or checking whether a bump's build actually passed.
---
# Merging Dependabot PRs
## Overview
Dependabot opens bumps against `main` and `support/struts-6-x-x`. Most land as-is.
A bump that **ships to users** needs a WW Jira ticket first, and the ticket ID must
reach the PR title, the PR body and the squash commit subject before it merges.
**Core principle: the decision is driven by the dependency's real Maven scope, not by
what Dependabot calls it.** Dependabot's `build(deps-dev):` prefix and its
`dependency-type:` trailer are guesses about *its own* ecosystem, not this project's
POM. Read the POM.
## The decision
Read the semver class from the Dependabot commit trailer — never parse version strings:
```bash
gh pr view <N> --json commits --jq '.commits[].messageBody' \
| grep -E 'dependency-name:|update-type:'
# update-type: version-update:semver-{patch|minor|major}
```
Then find the real scope, and cross the two:
| What is being bumped | patch | minor | major |
|---|---|---|---|
| GitHub Action (`.github/workflows/*`) | merge bare | merge bare | merge bare |
| Maven **plugin** or build tooling (`<build><plugins>`, `*-maven-plugin`, `maven-wrapper`) | merge bare | merge bare | merge bare |
| Maven dep, `<scope>test</scope>` | merge bare | merge bare | merge bare |
| Maven dep in `apps/` (showcase, rest-showcase) | merge bare | merge bare | merge bare |
| Maven dep, **compile / runtime / provided** — including `<optional>true</optional>` | merge bare | **TICKET** | **TICKET** |
"Merge bare" always still requires a green build (see below).
### Finding the real scope
```bash
grep -rn '<artifactId>NAME</artifactId>' --include=pom.xml . | grep -v /target/
```
Read the `<scope>` on the surrounding `<dependency>` block in the module that declares it
(`core/pom.xml`, `plugins/*/pom.xml`), not the `<dependencyManagement>` copy in
`parent/pom.xml` — the managed block usually carries no scope.
- No `<scope>` element means **compile** — it ships.
- `<optional>true</optional>` still ships: it is published in the module's POM and users
who opt in inherit the version. Optional is not exempt.
- For a version property (`jackson.version`, `byte-buddy.version`), resolve the property to
the artifacts it feeds and take the widest scope among them.
## Is the build actually green?
`.asf.yaml` makes exactly one context required per branch:
`Build and Test (JDK 17)` on `main`, `Build and Test (8)` on `support/struts-6-x-x`.
```bash
gh pr view <N> --json mergeStateStatus,statusCheckRollup --jq \
'"\(.mergeStateStatus) build=\([.statusCheckRollup[]
| select((.name // "") | startswith("Build and Test"))
| (.conclusion // .state)] | unique | join(","))"'
```
- Green = every `Build and Test *` job is `SUCCESS`.
- `continuous-integration/jenkins/pr-merge` is **not** a required context and flakes red
("This commit cannot be built"). `mergeStateStatus: UNSTABLE` with all build jobs green
is mergeable. Ignore Jenkins.
- Any `Build and Test *` failure, or `mergeStateStatus: BLOCKED` — **stop**. Report it and
move on to the next PR.
## Checkpoint — classify, then stop
Present one row per open PR and **wait for approval** before any Jira write, title edit or
merge. Required columns:
| PR | Base | Bump | Semver | Real scope (+ where declared) | Build | Action |
Only after approval, run the recipes below.
## Landing a bare bump
```bash
gh pr merge <N> --squash --subject "<exact current PR title> (#<N>)"
```
`del_branch_on_merge: true` is set, so no `--delete-branch`.
## Landing a ticketed bump
Five steps, in order. All five are required.
**1. Create the ticket** — `mcp__asf-issues__jira_create_issue`:
```json
{
"project_key": "WW",
"issue_type": "Dependency",
"summary": "Bump <group:artifact or property> from <old> to <new>",
"description": "Bump <group:artifact or property> from <old> to <new>",
"components": "<affected module>",
"additional_fields": "{\"priority\": {\"name\": \"Trivial\"}, \"fixVersions\": [{\"name\": \"<branch release version>\"}]}"
}
```
- `components` is its **own** parameter (comma-separated names), not a member of
`additional_fields`. `additional_fields` must be a JSON **string**, not an object.
- Issue type is **Dependency** — not Task, not Bug.
- Component is the module that declares the dep: `Core`, `Plugin - REST`, `Plugin - JSON`,
`Unit Tests`, `Build Management`, …
- Fix version is the one the latest tickets on the same line already carry:
`project = WW AND fixVersion in unreleasedVersions() ORDER BY created DESC`.
Not the pom SNAPSHOT: after 6.12.0, `support/struts-6-x-x` read `6.12.1-SNAPSHOT` while
its next release was 6.13.0. Not the lowest unreleased Jira version either: a shipped
version can stay unreleased in Jira for days.
**2. Retitle the PR** — insert the ticket after the conventional-commit prefix, leave every
other character alone:
```bash
gh pr edit <N> --title "build(deps): WW-XXXX bump org.htmlunit:htmlunit from 4.21.0 to 5.1.0"
# ^^^^^^^^ inserted; prefix and remainder verbatim
```
**3. Add the Closes line to the body** — its own paragraph, directly after the first line
(`Bumps …`). Some bodies have no release notes and so no `<details>` before the commands
footer; anchor on the `Bumps` line, never on `<details>`. Preserve the rest exactly:
```
Bumps [org.htmlunit:htmlunit](https://github.com/HtmlUnit/htmlunit) from 4.21.0 to 5.1.0.
Closes [WW-XXXX](https://issues.apache.org/jira/browse/WW-XXXX)
<details>
```
**4. Merge with the ticket in the squash subject:**
```bash
gh pr merge <N> --squash --subject "build(deps): WW-XXXX bump org.htmlunit:htmlunit from 4.21.0 to 5.1.0 (#<N>)"
```
Without an explicit `--subject`, GitHub takes the subject from Dependabot's *commit*
headline and the ticket ID is silently lost from git history.
**5. Hand the ticket back, still Open** — list it under the merge report as ready to
resolve once *every* PR on it has merged. The ticket workflow belongs to the release
manager; do not transition it unless asked. When asked:
```json
{"issue_key": "WW-XXXX", "fields": "{\"status\": \"5\", \"resolution\": {\"name\": \"Fixed\"}}", "return_fields": "status,resolution"}
```
`"5"` is the id of the *Resolve Issue* transition. The status name (`"Resolved"`, `"Closed"`)
and the transition name (`"Resolve Issue"`) all fail with "Could not find transition"; only
the id works. Verified on WW-5755.
## One dependency, two branches, one ticket
Dependabot opens the same bump separately against `main` and `support/struts-6-x-x`. Before
creating anything, look for the twin:
```bash
gh pr list --state open --author app/dependabot --json number,title,baseRefName
```
If a twin exists, create **one** ticket, put it in both PR titles/bodies, and list **both**
fix versions on it (WW-5649 carries 6.11.0 and 7.3.0 for PRs #1760 and #1763). It is ready
to resolve once both have merged.
The twin may already have merged in an earlier run, so `--state open` will not show it. Search
Jira too: `project = WW AND issuetype = Dependency AND summary ~ "<artifact>" ORDER BY created DESC`.
If the same bump already has a ticket, reuse it and add this branch's fix version to it.
## Scope of this skill
Triage and land the queue. Do **not**, as part of it, open follow-up PRs — `dependabot.yml`
ignore rules, license-header restorations, test-harness fixes. Report such findings in one
line under the table and let the decision be made separately.
Any bump with a red build is real compatibility work, not a merge. Report the root cause
(first `[ERROR]` lines of the failing `Build and Test` job) and stop. If asked to defer it:
file a `Dependency` ticket at the next major with the root cause, then close the PR with a
comment that gives the cause, links the ticket, and ends in `@dependabot ignore this major
version` — Dependabot replies "OK, I won't notify you…" (Hibernate 7 on #1905, Weld 7 on
#1998). That is the ignore rule; no `dependabot.yml` PR. Never push a fix onto a
Dependabot branch — Dependabot stops rebasing it, and a source change lands under a
ticketless `build(deps):` title.
## Traps
| Trap | Reality |
|---|---|
| "It says `build(deps-dev)`, so it's a dev dependency" | That prefix is Dependabot's guess. `commons-validator` arrives as `deps-dev` and is test scope (true), but the prefix is not evidence — the POM is. |
| "`dependency-type: direct:production`, so it ships" | `maven-wrapper` and `hibernate-core` both say `direct:production`; one is build tooling, the other ships. Read the POM. |
| "`<optional>true</optional>` means users don't get it" | It is in the published POM and pins the version for anyone who opts in. Optional compile deps need a ticket. |
| "git log shows no `WW-` on dependency commits, so titles aren't rewritten" | The squash *subject* historically came from Dependabot's commit headline while the *PR title* carried the ticket. Compare `gh pr view 1746 --json title` against `1f1674411`. Step 4 above exists to close that gap. |
| "A red check means don't merge" | Only `Build and Test *` counts. The ASF Jenkins context is not required and flakes red. |
| "Both branches need their own ticket" | One dependency, one ticket, two fix versions. |
| "The pom says `6.12.1-SNAPSHOT`, so the fix version is 6.12.1" | The pom is a placeholder. Jira's latest tickets on the line carry the real next version. |
| "The build is green, so I can just merge it" | Green is necessary, not sufficient. Classify first, and stop at the checkpoint. |
## Red flags — stop and re-read
- About to run `gh pr merge` before presenting the classification table and getting approval
- About to run `gh pr merge --squash` on a ticketed bump without `--subject`
- Classified a dependency from the PR title alone, without grepping the POM for its scope
- Creating a second ticket for a dependency that already has an open twin PR
- Opening a follow-up PR that nobody asked for
- Creating a Jira issue whose type is anything other than `Dependency`
- Transitioning a ticket nobody asked you to resolve
- Reading the fix version off the pom
## Related
- `creating-version-notes` — ticketed bumps appear in the Version Notes Dependency section;
bare bumps deliberately do not, and their absence is not a reconciliation gap.
More agent context in apache/struts
8 other files this repository gives its agents.
AGENTS.md
CLAUDE.md
Skill
- creating-release-vote-mail.claude/skills/creating-release-vote-mail/SKILL.md
- creating-security-bulletins.claude/skills/creating-security-bulletins/SKILL.md
- creating-version-notes.claude/skills/creating-version-notes/SKILL.md
- releasing-struts.claude/skills/releasing-struts/SKILL.md
- triaging-security-reports.claude/skills/triaging-security-reports/SKILL.md
- code-review.github/skills/code-review/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
Reports can't be read right now.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

