Merge Dependency PRs
Work through open dependency-update PRs in one or more repositories: identify them, merge the ones that pass CI, fix the ones that fail, and skip anything that would trigger a release.
Covers Renovate, Dependabot, and manual bumps for packages, dev dependencies, and repo tooling. Use when asked to merge pending PRs, land dependency updates, process dep PRs, or clean up the PR queue.
Detect platform
git remote get-url origin
| Remote pattern | Platform | CLI tool |
|---|---|---|
github.com |
GitHub | gh |
gitlab.com or self-hosted GitLab |
GitLab | glab |
All examples below use gh. For GitLab, substitute glab mr for gh pr, glab ci for gh run.
Step 1 — List and classify PRs
GitHub:
gh pr list --repo <owner>/<repo> --state open \
--json number,title,headRefName,author,mergeStateStatus,statusCheckRollup \
--jq '.[] | {number, title, state: .mergeStateStatus}'
GitLab:
glab mr list --state opened
Keep (dependency updates):
- Title matches:
chore(deps),fix(deps),bump X from,update X to,upgrade X,chore: upgrade <tool>, etc. - Author is
renovate[bot],dependabot[bot], or a human doing a manual dep bump - Head branch matches:
renovate/*,dependabot/*,chore/upgrade-*
Skip always — do not merge:
Any PR whose purpose is to trigger a release:
- Title is
"Version Packages"(changesets) - Title matches
"Release X.Y.Z"or"chore(release): ..."(semantic-release) - Author is
semantic-release-bot,release-please[bot], or similar release automation - Any PR clearly adding features or fixing bugs unrelated to dependencies
If working across multiple repos, repeat for each repo before proceeding.
Step 2 — Sort by CI status
For each dep PR, note its CI result:
| Status | Action |
|---|---|
| All checks pass | Merge immediately (Step 3) |
| Pending | Wait or move on; revisit after other PRs |
| Failing | Diagnose (Step 4) |
| BEHIND base | Update branch first, then re-check CI |
| Obsolete (base already has the fix) | Close with a note (Step 8) |
Check if the base branch already contains the fix before spending time on a failing PR:
# Example: check if main already has a specific version
git show origin/main:package.json | grep '"packageName"'
Step 3 — Merge passing PRs
GitHub:
gh pr merge <number> --repo <owner>/<repo> --squash
GitLab:
glab mr merge <number> --squash
If blocked by branch protection (mergeStateStatus = BLOCKED or BEHIND):
# Update branch first (GitHub)
gh api --method PUT repos/<owner>/<repo>/pulls/<number>/update-branch
# Then re-check CI; once passing:
gh pr merge <number> --repo <owner>/<repo> --squash
If the only blocker is a required check that already passed on an older commit, use --admin sparingly:
gh pr merge <number> --repo <owner>/<repo> --squash --admin
Step 4 — Diagnose failing PRs
GitHub:
gh run list --repo <owner>/<repo> --branch <branch> --json databaseId,name,conclusion --jq '.[:3]'
gh run view <run-id> --repo <owner>/<repo> --log-failed 2>&1 | grep -v "##\[" | grep -E "(ERR|Error|error|FAIL|Cannot)" | head -30
GitLab:
glab ci list --branch <branch>
glab ci view <pipeline-id>
For more context:
gh run view <run-id> --repo <owner>/<repo> --log-failed 2>&1 | grep -v "##\[" | tail -60
Step 5 — Fix common failures
The patterns below are common examples. For unfamiliar failures, read the full log and reason by analogy — identify what broke, check if the dep bump introduced a breaking change, and apply a targeted fix.
Package manager: ignored build scripts
Symptom: [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @parcel/watcher, esbuild, fsevents (pnpm 11+)
Fix: add the packages to onlyBuiltDependencies in pnpm-workspace.yaml:
onlyBuiltDependencies:
- '@parcel/watcher'
- esbuild
- fsevents
Check what other repos in the org use and match the pattern.
Package manager: invalid version in fixture
Symptom: version has an incorrect type, expected a string, but received null
Fix: find and fix all "version": null entries in test fixtures:
grep -rl '"version": null' . --include="package.json" | grep -v node_modules
# replace with "version": "0.0.0"
Build tool: pipeline/tasks rename (Turbo v2)
Symptom: Found 'pipeline' field instead of 'tasks'
Fix: In turbo.json, rename "pipeline" → "tasks" and update $schema:
{
"$schema": "https://turbo.build/schema.json",
"tasks": { ... }
}
Linter/formatter: new rules after version bump
Symptom: formatter or linter reports N errors after a version bump (e.g. Biome, ESLint, Prettier)
Fix:
- Run auto-fix:
<linter> check --fix .or<formatter> --write . - Verify no residual errors
- If the tool incorrectly processes certain file types, exclude them in the config
- Restore any incorrectly reformatted files from git:
git checkout -- '*.ext' - Commit the reformatted files separately from the dep bump commit
Merge conflicts / stale PR
Symptom: mergeStateStatus: DIRTY or CONFLICTING
Fix options:
- Rebase the PR branch onto the base:
git fetch origin git checkout -b <fix-branch> origin/<pr-branch> git rebase origin/main # resolve conflicts, then push git push origin <fix-branch> - For Renovate/Dependabot PRs with many accumulated changes, close the stale PR and let the bot re-open a fresh one.
TypeScript compatibility (major dep bumps)
Symptom: TS parse errors after upgrading a library that requires a newer TypeScript version
Fix: Upgrade TypeScript in the affected package. For monorepos, add a workspace override to unify versions:
# pnpm-workspace.yaml
overrides:
typescript: '~5.x.x'
Module resolution (ESM fully-specified)
Symptom: Module not found: Can't resolve 'react/jsx-runtime' + BREAKING CHANGE: The request failed to resolve only because it was resolved as fully specified
Fix: configure your bundler to disable fullySpecified resolution for .mjs files. Example for webpack:
config.module.rules.unshift({
test: /\.m?js/,
resolve: { fullySpecified: false }
})
CI workflow not triggering after push
Symptom: pushed fixes to a PR branch but the workflow never fires
Likely cause: The PR contains workflow file changes. GitHub may require a maintainer action to re-trigger.
Fix: Close and reopen the PR. If that doesn't work, create a fresh PR from a new branch (cherry-pick only the dep bump commit, excluding workflow file changes).
Step 6 — Handle changeset requirements
If CI reports a missing changeset check failure:
- Determine whether the repo publishes to a package registry and uses a changeset tool
- If yes, create a changeset for the dep bump (typically a
patchbump with summary "Update dependencies.") - If the repo has a
add-changesetor equivalent skill available, invoke it
Do not add a changeset if:
- The repo is private or doesn't publish to a registry
- The PR only bumps devDependencies with no runtime impact
- The CI does not have a changeset check
Step 7 — Commit, push, and monitor
After applying fixes:
git add <changed-files>
git commit -m "fix: <description of what was fixed>"
git push origin <branch>
Watch CI:
# GitHub
gh run list --repo <owner>/<repo> --branch <branch> --limit 3
gh run watch <run-id> --repo <owner>/<repo>
# GitLab
glab ci list --branch <branch>
Once CI passes, merge (Step 3). Then move on to the next PR in your list.
Step 8 — Close obsolete PRs
A PR is obsolete when the base branch already contains the intended change:
git show origin/main:package.json | grep '"packageName"'
GitHub:
gh pr close <number> --repo <owner>/<repo> \
--comment "main is already on <version>; closing as superseded."
GitLab:
glab mr close <number> --note "main is already on <version>; closing as superseded."
Checklist summary
- Listed all open PRs and filtered to dep updates only
- Confirmed no release PR is in the merge list
- Merged all PRs with passing CI
- Diagnosed and fixed all failing PRs (or noted as needing deeper investigation)
- Handled changeset requirements where applicable
- Closed any PRs that are now obsolete