Skip to content

Docs / Dependency bots

Dependency bots (Dependabot, Renovate)

How developerz.ai reviews, fixes and merges Dependabot and Renovate pull requests through its gated merge, which keys decide it, and a recommended dependabot.yml.

developerz.ai never holds a conversation with another bot: it does not reply to a Dependabot or Renovate comment, and no bot-on-bot thread starts. It does finish their pull requests. On a repository whose policy says the platform merges dependency bots, each bump is reviewed, its red CI is fixed, and it is merged through the same gated merge as the platform's own work.

Which dependency pull requests the platform takes

Dependabot (dependabot[bot], dependabot-preview[bot]) and Renovate (renovate[bot], renovate-bot) are recognised by exact login. A pull request by one of them is taken when all three hold:

  1. The repository ships .dz/maintainer/maintainer.yml. With no policy file, auto-merge resolves off. On the free and verified-OSS tiers it stays off unless the file sets pr.auto_merge: true explicitly.
  2. pr.auto_merge resolves on (the default once the file exists).
  3. pr.auto_merge_scope admits the author:
pr.auto_merge_scope Dependency-bot pull requests
any (default) taken
own_and_dependency_bots taken; every other non-platform author is left alone
not_project taken; so are pull requests by people without write access to the repo, never a project member's (write, maintain or admin)
own left alone

Under not_project a dependency bot needs no lookup; a human author's write access is read from GitHub (GET /repos/{owner}/{repo}/collaborators/{username}/permission), so the App installation must be able to read that repository's collaborator permission. An answer is remembered for 10 minutes. When GitHub does not answer, the platform does nothing with that pull request, says so on it, and asks again a few minutes later.

Review and merge ask the same question, so a dependency pull request is reviewed exactly when the platform would merge it. One the policy does not cover is not reviewed, not watched and not merged: it stays yours, and the review lane's audit row names the key that left it out (auto_merge off, the scope, or a policy file that failed validation).

What happens to a pull request it takes

  1. Review. The built-in reviewer reviews it like any pull request, on your model key and your box. That review is what satisfies pr.require_review (on by default), so a bump does not wait forever for a review nobody will write. An active CodeRabbit does not stand the reviewer down here, because CodeRabbit does not review bot-authored pull requests. A login listed in .coderabbit.yaml reviews.auto_review.ignore_usernames is still skipped.

  2. CI. The pull request is watched. With ci.self_fix on (the default), a red check is handled in the order a maintainer would handle it:

    • The failed job is re-run once. A GitHub Actions job that failed on its first attempt is re-run before anything else, once per commit, so a flaky test costs a re-run and no code changes. (This needs the App's actions: write permission. Where an installation has not granted it, the re-run is skipped and the next step runs.)
    • A check that is red on your base branch too is not the bump's fault. If the same check fails on the tip of the base branch, no fix is pushed to the pull request: it could not fix it, and the cheap way to make it green from inside a bump is to weaken the test. The pull request is held, a fix for the base branch is requested once per break, and when the base is green again the branch is updated from it (a merge of the base, never a force-push) so CI re-runs and the merge goes ahead.
    • Otherwise a fix round runs on the pull request's branch. It first checks whether the failure reproduces on the base branch without the bump. If it does, it changes nothing, posts the evidence on the pull request and reports "not this pull request". The platform then requests the fix on your base branch (one request per failure, with that evidence), holds the bump, and when the base moves it merges the base in and carries on by itself, up to three times. You are not asked for anything unless that still leaves the check red. If the bump caused it, the round reads what the new versions changed (the bot's description, with its release notes, is handed to the round as a file) and adapts your code.
    • A bump that cannot be adapted is closed, with the reason. When the round finds the new version genuinely incompatible, the platform writes which package and why on the pull request and closes it. It sends the bot no command. Closing tells Dependabot or Renovate not to propose those exact versions again, which is the intended result; the bot opens a new pull request by itself when a newer release appears. Reopen the pull request if you want that version anyway.
    • If the round stops without saying which of the two it was, you are told (ci_fix_declined) and the pull request waits for you.

    A pull request whose failing checks stop going down is escalated to you instead of spending more rounds. A bump that breaks CI is adapted to, or escalated, never reverted. The bump is the pull request's whole purpose, and the fix instructions say so: change your code (its callers, types, configuration and tests) to work with the new version. If the code cannot be adapted, the fix changes nothing and posts why on the pull request. It is told never to weaken, skip or delete a test, lower a threshold or edit CI configuration to get green. Before anything is pushed, the platform checks the fix commit against the pull request's own change and refuses it in two cases: the fix would leave the pull request with no changes against your base branch, or it would set every version the bump raised back to the version on your base branch. Pinning part of a grouped bump back, for example one library whose new version breaks while the rest of the group stays raised, is allowed. A refused fix pushes nothing, is not retried, and the pull request is escalated to you as pr_intent_reverted: adapt the code yourself, or close it.

  3. Merge. Through the merge gate, never around it: CI green, pr.required_checks passing, a review, your branch protections, your pr.human_merge lanes and your escalate.ask_before_acting categories.

  4. Held for you when a gate needs a person: the pull request goes into your approvals list. Approve that exact head (the dashboard, or pr approve_pending) and the platform merges it; or merge it yourself.

A held pull request says why. When the platform has been working on one commit of a dependency pull request for 30 minutes without merging it or asking you for anything, it writes one comment on the pull request and keeps it up to date in place: which check is red, whose failure the evidence says it is, what was done (a re-run, a fix round and whether it is running or still waiting for a machine that can run your repository), and what happens next. If it later needs you, that same comment becomes the "Needs a human" notice. You should never have to guess what a quiet pull request is waiting for.

Superseded pull requests. Dependabot closes its own pull request when a newer one covers the same package, and Renovate updates its pull request in place. The platform does not close superseded pull requests for them: when the bot closes one, the watch on it ends and no fix is pushed to the dead branch. The only dependency pull request the platform closes itself is one whose bump cannot be adapted (above).

Do not add a separate auto-merge workflow

Leave merging to the platform. Do not add:

  • a GitHub Actions workflow that runs gh pr merge --auto (or an auto-merge action) on Dependabot pull requests,
  • Renovate's automerge: true,
  • GitHub's native auto-merge switched on for these pull requests.

Each of those merges when GitHub's branch protection is satisfied, and nothing else. They skip every gate the platform adds on top: the review, human_merge lanes, the major_dep_bump hold and the audit record of who merged what and why. The platform's merge is the gated one; a second merger beside it is an ungated one.

If you already have one. The platform does not fight it and never disarms auto-merge another actor armed. Whichever merger is satisfied first lands the pull request; when it merges underneath the platform, the watch ends cleanly and nothing further is pushed or posted. What you get is a race with two different outcomes: a bump whose checks are green before the platform's review posts is merged by the workflow, unreviewed and outside your ask_before_acting list, while a red one is left to the platform. To put every dependency pull request behind the same gates:

  1. Delete the workflow (commonly .github/workflows/dependabot-automerge.yml), or remove its gh pr merge --auto step.
  2. On pull requests that are already open, switch auto-merge off (gh pr merge --disable-auto <number>, or the button on the pull request).
  3. Keep "Allow auto-merge" in the repository settings as you like; with nothing arming it, it does nothing.
# .dz/maintainer/maintainer.yml
version: 1
pr:
  auto_merge: true
  # Merge the platform's own pull requests and dependency bots, nobody else's.
  # Omit it (the default `any`) to merge every author's.
  auto_merge_scope: own_and_dependency_bots
  require_review: true
  human_merge:
    paths:
      # GitHub Actions bumps change what your CI runs: a person decides those.
      - ".github/workflows/**"
escalate:
  # Written out: a dependency bot's pull request is held only by a category you WRITE here.
  # The default list still applies to people's pull requests, never to the bots'.
  ask_before_acting: [breaking_change, major_dep_bump, first_time_contributor, large_pr]

On a repo with auto_merge: true, the platform's own pull requests and Dependabot's or Renovate's are held for you only by an ask_before_acting category your file writes. The default list (breaking_change, major_dep_bump, first_time_contributor, large_pr) applies to pull requests people open; with no escalate: block, a bot's major bump that is green and reviewed merges. Write major_dep_bump (as above) to be asked first.

major_dep_bump holds a dependency-bot pull request whose title states a semver-breaking bump (from 1.4.2 to 2.0.0, and any 0.x minor such as 0.9.0 to 0.10.0; for Renovate, a title ending to v19 or (major)). A grouped update's title states no versions, so the merge gate reads the list the bot writes in the pull request description (Dependabot's Updates `pkg` from 1.4.2 to 2.0.0 lines or its table, Renovate's update table) and holds the group when any package in it is a breaking bump. Keeping majors out of groups, as below, is still the better setup: each major then arrives as its own pull request, and the minors and patches beside it are not held with it.

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"        # one entry per ecosystem your repository uses
    directory: "/"
    schedule:
      interval: "weekly"
    cooldown:
      default-days: 14
    groups:
      minor-and-patch:
        patterns: ["*"]
        update-types: ["minor", "patch"]
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    cooldown:
      default-days: 14
    groups:
      actions-minor-and-patch:
        patterns: ["*"]
        update-types: ["minor", "patch"]
  • cooldown: default-days: 14 on every ecosystem. A delay, not a guarantee: Dependabot opens no version-update pull request for a release younger than 14 days. A hijacked or broken release is often caught and pulled within that window, so the delay lowers the odds of one reaching a pull request the platform would merge; it verifies nothing about a release that is still bad after 14 days. Dependabot's security updates bypass the cooldown.
  • Group minor and patch updates only. One pull request per ecosystem per week instead of one per package, while majors stay separate so major_dep_bump can hold them.
  • No auto-merge settings. Merging is the platform's job (above).

For Renovate, the same posture in renovate.json:

{
  "extends": ["config:recommended"],
  "minimumReleaseAge": "14 days",
  "packageRules": [
    { "matchUpdateTypes": ["minor", "patch"], "groupName": "minor and patch" }
  ]
}

Leave Renovate's automerge unset (it defaults to off).

Checking on it

  • A dependency pull request that is not moving: read the platform's status comment on it (above), then its audit rows. A review the bot deferred names the key that left the author out; a merge the gate refused names the gate.
  • Escalations lists the ask_before_acting categories.
  • The maintainer.yml reference has every pr key.