Six Dependabot PRs, Zero Merged

Dependabot opened six pull requests against my wiki repo over the last two weeks. I merged none of them. I closed all six and replaced them with two hand-assembled PRs, and then I wrote a unit test so it wouldn’t happen a third time.

The scoreboard, in order:

PR Update Outcome
#108 @aws-sdk/client-s3 3.1090.0 to 3.1113.0 closed
#109 auth 1.6.23 to 1.7.1 closed
#110 typescript 6.0.3 to 7.0.2 closed
#114 all three, by hand merged
#115 astro and @astrojs/node closed
#116 lucide-react 1.17.0 to 1.33.0 closed
#117 @types/node 25.9.1 to 26.2.0 closed
#119 all three, by hand merged

Same shape twice, eight days apart. That’s the part that made me stop and look at the config instead of just closing tabs again.

Why none of them could land alone

Dependabot’s default model is one PR per dependency, and that model quietly assumes your dependencies are independent. Mine weren’t.

The TypeScript bump is the cleanest example. Going from 6 to 7 isn’t a version string change in this repo, because TypeScript 7 doesn’t accept baseUrl. My tsconfig.json had it. A PR that changes only the version number fails typecheck immediately, and Dependabot has no way to also delete the config line that made the old version work. It can bump. It can’t migrate.

The Better Auth pair was the same problem wearing a different hat: the runtime and the CLI have to move together or you end up with two Better Auth versions resolved in one tree, which then desynchronizes the generated schema and the proof scripts that check it against the live database.

Astro was the third variant. Astro 7 changed the default value of compressHTML from true to 'jsx', which strips whitespace using JSX rules instead of HTML-aware rules. Whitespace between inline elements that used to survive now collapses. A bare version bump is a rendering change to every page in the wiki, and nothing in the PR would tell you that.

Three failures, three different mechanisms, one common cause: an upgrade is a code change, and the bot only sends you the version string.

The config was making it worse

Here’s what my .github/dependabot.yml said before:

    ignore:
      - dependency-name: "*"
        update-types: ["version-update:semver-patch"]
    labels:
      - "dependencies"

That ignore block is a common piece of advice, and it’s backwards. Patch releases are the ones you want landing on their own, because they’re where the security fixes go and they almost never break anything. Suppressing them means the only PRs that reach you are the majors and minors, which are exactly the ones that need coordination. I’d configured the bot to send me nothing but the hard cases, one at a time, in whatever order it felt like.

The replacement groups everything:

    groups:
      npm-dependencies:
        applies-to: "version-updates"
        patterns:
          - "*"
        update-types:
          - "major"
          - "minor"
          - "patch"
      npm-security-updates:
        applies-to: "security-updates"
        patterns:
          - "*"

applies-to takes one value, and defaults to version updates when you leave it off, so grouping version updates and security updates means two named groups rather than one clever one. That caught me on the first pass.

Then I did the part I’d argue about

I wrote a test that asserts the contents of my Dependabot config. It’s 94 lines of Vitest in scripts/dependency-policy.test.ts, and it parses YAML files as fixtures:

expect(versionGroup).toMatchObject({
  'applies-to': 'version-updates',
  patterns: ['*'],
  'update-types': ['major', 'minor', 'patch'],
});
expect(npmUpdate?.ignore ?? []).not.toEqual(
  expect.arrayContaining([
    expect.objectContaining({
      'dependency-name': '*',
      'update-types': expect.arrayContaining(['version-update:semver-patch']),
    }),
  ]),
);

That second assertion is the whole point. It doesn’t check that the config is good, it checks that the specific bad config I already shipped once can’t come back. Someone pastes the ignore-all-patches snippet from a blog post, and the suite goes red with a test name explaining why.

Two more assertions came along for the ride. One keeps the Astro major aligned with the Vite major I force through overrides, since those two are joined at the hip and a bot doesn’t know that. The other is a single line:

it('preserves Astro 6 HTML whitespace behavior under Astro 7', () => {
  expect(readProjectFile('astro.config.mjs')).toContain('compressHTML: true');
});

An upstream default changed underneath me, I made a deliberate choice to opt out of the new one, and now that choice is pinned by a test that names the reason. In six months when I’m reading compressHTML: true and wondering why it’s set to the value the docs call the old behavior, the test name answers me.

Did it work

PR #120 opened this morning: “bump the npm-dependencies group with 10 updates.” One pull request, ten packages, one CI run, one review. Whether it merges is a separate question. But I’m reviewing a coherent upgrade instead of triaging ten pieces of one.

Worth noting what did merge cleanly two weeks ago, before any of this: three bot PRs bumping happy-dom, biome, and tsx. All dev dependencies, all leaves, nothing else in the tree cares what version they are. Dependabot’s default model works fine for those. It fell over the instant it touched something load-bearing, which is the same instant I actually needed help.

Testing your CI config sounds like a joke until you’ve watched a YAML file silently revert twice.

Sources

I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].

Astro Typescript Github Tooling Dependabot