The Newbie’s Fault

DevOps circles like to say that a good onboarding lets a new hire ship to production on their first day. This week I received my Apache Maven commit bit, and — true to that spirit — by day two I had shipped a broken integration test into the project. The catch that makes it worth telling: the test was not wrong about the feature it guarded — it tripped over its own setup. I did not get away with that by being clever; I got away with it because Maven has a community that pulls a newcomer’s chestnuts out of the fire within hours.

Day two, and already a confession

Becoming an Apache Maven committer is a quiet, friendly affair: an invitation, a welcome thread, and suddenly write access to one of the most-used build tools on the planet. I wanted to be useful immediately, so I picked up a small, well-understood backport — exactly the kind of low-risk task a fresh committer should start with.

What could possibly go wrong?

What I did — and what I leaned on

The change itself is unremarkable. An issue (#12288) described that a settings.xml profile activated via activeByDefault did not propagate its properties to the local repository manager in time. I knew this fix well, because the original was mine (apache/maven#12291). Another committer had refined it for the 4.1 line (apache/maven#12297) and backported it to 4.0 (apache/maven#12299) — Maven (currently) maintains three release lines in parallel (4.1, 4.0, and 3.x), and the change had to land on each. Having watched it sail cleanly onto two of them, I took the third hop myself — onto maven-3.10.x, for the next Maven 3 release — and it felt like a formality.

That confidence was the trap. On the 3.x line the production code and the integration tests live in two separate repositories, so the one backport had to ship as two pull requests:

One repository or two: Maven 3 vs. Maven 4

Maven 4 keeps its core and its integration tests in a single repository, so a fix and the tests that guard it travel together in one pull request. Maven 3 still splits them across two repositories — apache/maven and apache/maven-integration-testing — so the same change has to be coordinated across two pull requests. That split is what let my fix and its test drift apart in the first place.

And here is my actual mistake, the quiet one underneath the visible diff: riding that confidence, I leaned on the existing tests instead of reading them. I modeled the 3.x integration tests on the ones already there, reused the established harness pattern, and assumed that what worked for them would work for me. I trusted the scaffolding without grasping the machinery beneath it.

The tests also carry a version gate, so they only run once a new-enough Maven exists:

public MavenITgh12288SettingsProfileAetherPropertiesTest() {
    super("[3.10.0-SNAPSHOT,)"); // only runs when the Maven under test is >= 3.10.0-SNAPSHOT
}

In plain terms: the test stays dormant until a Maven 3.10 snapshot exists to run it against. At merge time, no CI run had one.

Both pull requests went green across the CI matrix, and both were merged. A clean first contribution — or so it looked.

Why the green was not real

Here is the core of it: my two pull requests never ran each other’s code. That is what turned a row of green checkmarks into a false signal.

Maven’s own tests are special — the thing under test is Maven itself. The integration-testing repository ships no Maven of its own; it injects one from outside. Its CI runs against a released Maven, so a test gated to [3.10.0-SNAPSHOT,) does not fail there. It is simply skipped. The core repository’s CI is the opposite: it builds the snapshot from source and runs the tests against it. My test could have run there — but it sat in the other repository, in a pull request the core build never saw. One side skipped my test; the other never had it. "All green" never meant "all passed." The one test that mattered had not run anywhere.

Had the test been merged first, the core change’s own CI would have built the snapshot and run it — and the failure would have surfaced right there, on the core pull request. The two changes live in different repositories, and nothing wired them together, so at review time the test ran in neither place.

The one case where they do meet: matching branch names

One setup does make the two sides test together — and I had missed it. When the CI of apache/maven builds a pull request, it looks for a branch of the same name in the contributor’s maven-integration-testing fork, and if it finds one, runs those integration tests against the freshly built snapshot. Name both branches identically on your forks, and the core pull request exercises your new test against your fix — exactly the pairing that was missing here, because my two branches had different names. The coupling runs in that one direction only: the IT repository’s own CI never builds core from a matching branch.

What the test setup actually did

A day later, that core CI built the snapshot and ran my brand-new test against it for the first time. It failed — and the reason was exactly the machinery I had copied without grasping.

To stay reproducible, an integration test should not reach out to the open internet and download whatever happens to be on the real Maven Central today. To avoid that, the test harness hands the build a prepared settings.xml by default — one that quietly redirects "Central" away from the real repository and onto a controlled mirror the test suite has staged in advance. The deal: resolve only what that mirror already contains, and your test stays deterministic.

My test broke that deal in a way I did not notice. To start clean, it first wiped its local repository — the on-disk cache of previously downloaded artifacts. Now look at what that left: an empty cache, and a "Central" that was really just the staged mirror. When the build then needed an everyday plugin (maven-install-plugin) that the mirror did not carry, it had nowhere left to get it. Not from the cache I had just emptied, and not from Central, because Central was no longer really Central. A perfectly ordinary plugin became unresolvable, and the build fell over.

The existing tests I had modeled mine on never hit this: they did not start from an empty cache, so the redirected Central never mattered to them. I had reused their shape without asking what their setup actually did — and mine did one extra thing that changed everything.

A community that catches you

This is the part of the story I actually want to tell.

Within a few hours, two long-time committers had diagnosed the failure, explained it, and shipped the fix. It was two lines (apache/maven-integration-testing#429): build the test’s Verifier with settings = null, so the harness injects no prepared settings at all — with no override in the way, "Central" is Central again, and the test can simply fetch the plugin it needs. No blame, no "who merged this," just a quick, friendly rescue and a clear explanation of what I had missed.

That responsiveness is the real reason a two-day-old committer can afford to make a mistake. I will not pretend I would have found that fix as quickly on my own.

Not just a Maven thing

My mistake was tiny, and it was caught before it could hurt anyone. The structure that hid it, though, is not Maven-specific at all.

Any project that splits tightly coupled parts across two repositories — production code in one, its tests in another — inherits the same chicken-and-egg. A green check on either side, on its own, proves nothing about the pair. Only the place that builds both together is a real gate; everything else can quietly pass by skipping the one thing you wanted to verify. And reusing an existing test as a template only carries you as far as you understand what that template actually does.

The takeaway

The honest lesson is bigger than "the new guy made a rookie mistake."

A skipped test and a passing test look almost the same from the outside, and the small contract that decides where a test really runs lived, in my case, mostly in experienced people’s heads. That is nobody’s fault — it is the kind of knowledge that feels obvious once you hold it and stays invisible until then.

What a newcomer can do is help close that gap a little. This article is my small contribution: it writes down one piece of that knowledge — how Maven 3’s two repositories differ from Maven 4’s single one — so the next person backporting a fix can see the trap coming.

Day two: a broken test, a green checkmark that meant nothing, and two committers who quietly put it right before I had even finished worrying. We ship a build tool here, not a running service — so there was never any real "production" to break. Still, as first weeks go, I will take it. Next time, I will read the test harness instead of copying it.