Repository navigation
ci(build): check in the Maven wrapper jar, and quarantine a test that fails its own setup guard - #440
Conversation
… Maven starts
CI lanes intermittently died about 11 seconds in, before Maven printed anything:
Error: Could not find or load main class org.apache.maven.wrapper.MavenWrapperMain
Caused by: java.lang.ClassNotFoundException: org.apache.maven.wrapper.MavenWrapperMain
Three runs on 2026-09-03, in lanes with nothing in common but the bootstrap:
#410 chaos (run 33700215189), #426 Lincheck (run 33705127539), and
#435 twice in one run (run 33753996337 - Lincheck and static: infer). The
infer lane passed on a plain re-run of the same head, so it was never the code.
Diagnosis: `.gitignore`'s blanket `*.jar` rule swept up `.mvn/wrapper/maven-wrapper.jar`,
which the Maven wrapper intends to be checked in. Without it, mvnw's "Extension to allow
automatically downloading the maven-wrapper.jar" block refetches it from Central on every
fresh checkout - so every job in every run makes that request - and it does so with
--quiet/--silent, discarding the jar with `rm -f` on failure. A refused or truncated
download therefore leaves no error text and no class, and the next line of mvnw runs java
against a classpath that does not exist. The failure is silent by construction.
Fix: track the jar and negate the ignore rule for that one path. The bootstrap then reads
from the tree instead of the network, so the failing step no longer exists on any lane.
Verified with a control arm, both halves on a fresh worktree of origin/master:
- Before: with wrapperUrl pointed at an unreachable host, `./mvnw -v` reproduced the CI
failure exactly - the same two lines, same exit 1.
- After: same unreachable wrapperUrl, same tree with the jar tracked, `./mvnw -v` printed
Maven 3.9.0 and never consulted the URL, because the `[ -r "$wrapperJarPath" ]` guard
short-circuits the whole download block.
- The tracked bytes are Maven Central's own: sha256
e63a53cfb9c4d291ebe3c2b0edacb7622bbc480326beaa5a0456e412f52f066a, identical to a fresh
download of maven-wrapper-3.2.0.jar, which is the version wrapperUrl pins.
Rejected: switching the wrapper to distributionType=only-script. It also removes the jar,
but it removes it by replacing both mvnw and mvnw.cmd with a different upstream script, on
lanes spanning ubuntu-latest, macos-latest and the self-hosted highcpu runner, plus every
bin/*.sh wrapper that shells out to ./mvnw. That is a behavioural change to the build
entrypoint of the whole repo to fix a fetch that a tracked file deletes outright. It also
leaves the distribution-zip download in place, which is the one thing both designs share -
so it does not buy a smaller network surface either.
Rejected: adding wrapperSha256Sum to maven-wrapper.properties as belt and braces, to make
a future download failure loud rather than silent. It breaks the build on macOS. mvnw
prefers `sha256sum` when present, and macOS Darwin 25 now ships /sbin/sha256sum (Darwin
1.0) whose -c reads checksum lines only from a file, never stdin - so mvnw's
`echo "$sum $jar" | sha256sum -c` prints a usage message and exits 1. Measured here: with
a correct jar and a correct sum, `./mvnw -v` failed with "Failed to validate Maven wrapper
SHA-256, your Maven wrapper might be compromised". The `shasum -a 256 -c` fallback fails
the same way and is never reached anyway. That would trade a rare CI flake for a certain
local one, and the repo has a macos-latest lane. Git already content-addresses the blob,
so the sum adds nothing the checkout does not guarantee.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #440 +/- ##
============================================
+ Coverage 81.86% 82.34% +0.48%
- Complexity 1466 1470 +4
============================================
Files 95 95
Lines 5133 5133
Branches 500 500
============================================
+ Hits 4202 4227 +25
+ Misses 732 709 -23
+ Partials 199 197 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
[superseded - a quarantined test changed outcome] 🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its No quarantined test changed outcome since the previous push. Updated for Superseded by a newer quarantine lane report. |
🟢 Throughput — OKThis branch measured about 10% slower than master, on the one test this measures. That is INSIDE this test's own run-to-run spread of about 17%, so read it as a reading and not as a result - re-running the same commit moves it by about as much.
Allowable range 🟢 ≥ 0.70 · 🟡 0.50–0.70 (about a 30% loss) · 🔴 < 0.50 (about a 50% loss) What the numbers mean, and what they cannot tell youThe one that gets misread. Why a shape and not a rate. A rate depends on which runner you drew. A shape does not: every test here processes a fixed number of records, so a runner twice as slow doubles the subject and the controls together and leaves their ratio alone. That is the whole trick, and it is why the reported rate is shown last and labelled as this machine only. Reading the comparison. By conservation, not by correction. Every test in this lane processes a fixed number of records, so within one run the ratio of one test's time to another's is invariant under machine speed — a runner twice as slow doubles both terms and leaves the ratio alone. There is no machine-index correction to be wrong, because nothing needed correcting. Per-method times, not class times. A class time is Reference is the median of 10 recent What this still cannot do. It removes machine-to-machine variance. It does not remove this test's own run-to-run variance, measured at about 30% on a single unchanged commit while its controls stayed within 5%. That is a property of the test, not of the comparison, and no arithmetic here can touch it — which is why the reference is a median and the bounds are deliberately coarse. 🟡 means look at this; only 🔴 is outside the measured spread. Runs used: c668acb, fb5ea93, 6573781, c30aaee, ca9c21b, 77fbba8, 7a8a7f0, 2e2705a, 28c6be6, c813942 Since the previous push: ratio 0.987 -> 0.899, share 1.515 -> 1.664, rate 70218 -> 83616 (+19.1%). One push of difference sits inside this test's measured spread - read it as movement, not as a result. Updated for |
|
@claude review this |
|
Claude finished @astubbs's task in 1m 14s —— View job Review
LGTM — the mechanism and the evidence both check out. Verified independently against this PR's checked-out tree (HEAD
Comments on the PR's own reasoning:
No findings. This is a well-diagnosed, minimally-scoped fix with a real control-arm verification already documented in the PR body. |
✅ Duplicate Code ReportTwo engines run in parallel for cross-validation. Each has its own thresholds tuned to its baseline - the real safety net is the per-engine "max increase vs base" check. ✅ PMD CPD
No new clones introduced by this PR. ✅ jscpd (language-agnostic)
No new clones introduced by this PR. Powered by astubbs/duplicate-code-cross-check |
… guard, not its assertion Quarantined on a recorded sighting ledger rather than a diagnosis, which rule 1 of docs/quarantined-tests.md allows and which is the honest description of what is known. WHAT FAILS IS THE PRECONDITION, NOT THE CLAIM. The awaited condition is the control thread reaching the mid-loop pause point at offset 25 that saturates the pipeline. It is not the confluentinc#909 signature assertion the test exists to make, so a failure here says nothing about the defect it reproduces - which is why leaving it red was costing a gate without buying a signal. A pass completes in about 10s; every recorded failure sits at the 30s timeout. That is the shape of a precondition the test cannot force under load, not a wrong answer. MASTER-STATE, ON EVIDENCE THAT OUTLIVES A CI LOG. `node bin/inflight.mjs codecov test RegistrationRaceStaleResidentIT` is the ledger: failures land on unrelated branches minutes apart while sibling branches pass in the same window, including branches touching core. It has failed on heads whose only content was a dependency bump and on heads whose only content was documentation. That is what rules out PR-state, and it is why this is not one PR's problem to carry. flapping = true, because it passes most runs - so the lane reports a pass rather than demanding the annotation be deleted. Unowned: no fix PR exists because no diagnosis does, and the registry entry says what would produce one rather than leaving that to be rediscovered. The guard waits on a pause point driven through PausableInsertShardManager, so the open question is whether the control thread never reaches offset 25 under contention or reaches it after the wait expired. Those must be separated before anyone touches a timeout: the rule that governs the load-tightness family governs this one, and a test failing under load may be exposing a real product bug. Carried on this PR rather than its own because this branch is merging next and both open PRs are blocked on the same red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015reBSoQQYreND4YnnEwduW
🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its Since the previous push: Updated for |
… new IT lands in the catch-all shard by design Two conflicts, both resolved by keeping both sides rather than picking one. docs/quarantined-tests.md: #440 quarantined RegistrationRaceStaleResidentIT on the recorded ledger (rule 1), which this branch had cited as master-state twice but not quarantined. That entry stays verbatim. Beside it, this branch's rewrite of the largeNumberOfInstances entry replaces master's "measured but not explained" text - the mechanism is measured now, and the registry is one of three places that said otherwise. docs/inflight/test-untracked-ci-flakes.md: master's row had reached 12 sightings, including #447's by-construction one on a docs-only branch; this branch's 2026-09-04 sighting on #444 and its same-head re-run control were not among them. Merged to 13 with both. Either side taken whole would have dropped a sighting under an authoritative-looking count - the same trap the previous merge of this file recorded. #442 sharded the integration lane; ClosingMemberRebalanceIT is not in HEAVY_CLASSES and lands in the catch-all by subtraction, which its design calls the safe direction: a new class runs by default. At ~52s for five cases it does not belong in the heavy set. ChaosScenarioBase auto-merged (#435's classification beside this branch's getMessage type fix); test-compile confirms it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WErnxQd9Ew57F9SsqzdPU5
Inherited #440, #447 and #435. Two of them touch this branch's ground. #440 QUARANTINES the registration-race test this branch has been recording sightings of. Nothing here needs to change for that - the ledger row and the quarantine registry are different instruments, and master keeps both - but it means a future red from that test is no longer this branch's problem to attribute. THE LEDGER ROW, CONFLICTED A THIRD TIME AND UNIONED AGAIN Both sides said "12 seen" and neither was a superset: they count DIFFERENT seventh sightings on 2026-09-03 - master's is #438, this branch's is #433 - so the union is thirteen. A count that matches on both sides is the easiest kind of conflict to resolve wrongly, because taking either side whole looks like a no-op and silently drops one real sighting. Master's #438 control is the stronger of the two and is kept as such: that branch's whole diff is `bin/` and `docs/`, compiling no Java, so the tree built there IS master's - branch content is excluded by construction rather than by comparison. This branch's within-branch control is kept beside it because it establishes something that one cannot: one tree, both outcomes, which excludes the tree itself and leaves only the runner. They are different arguments, not two tellings of one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C3KnLcX4KibVMCP6sMfNgM
…lease note does too Master quarantined RegistrationRaceStaleResidentIT on a sighting ledger (#440) while this branch was open. The opening block this PR corrected named only largeNumberOfInstances as the entry keeping the release gate red; it now names both and still defers to the registry as the enforced copy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SVm2cT6ZgUyPMim9CukYK
…encing-brainstorm Fifteen commits on top of the sixteen the previous merge took, three conflicts: - PartitionState: master's javadoc on onSuccess(long) - the three closed shapes of its assert - with this branch's cross-reference to the container overload beneath it. - TransactionalClaim: master's 2026-09-07 scope note ends C14; this branch's C15 follows it. C4 and C9 move to REFUTED as master decided (#436); C15 is untouched by that finding. - The flake ledger: master's rows where both sides have one (the registration-race guard is now quarantined on master, #440), plus the three rows only this branch carries. One inherited instruction is NOT applied here: #450 asks this branch's replay loop to take the same register-then-publish swap it made in maybeRegisterNewPollBatchAsWork, and records that in the refactoring backlog. The commit after this one does it, with the pin. The paragraph in master's revoke-drain note that names this PR's stacking is attested post-merge: it records where #408 sat when the split was decided. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VrpH51xNDodaajE4P2nhFg
…ack pressure had stopped fetching RegistrationRaceStaleResidentIT failed across CI for a fortnight on its own SETUP guard - "control thread must reach the mid-loop pause point (offset 25)" - never on the confluentinc#909 assertion it exists to make, so every failure was evidence about nothing. #440 quarantined it on a sighting ledger with no mechanism. This is the mechanism, and it is the test's staging, not PC. DIAGNOSIS. The test saturates the pipeline by gating the user function so nothing ever completes, which makes whatever stage 1 produced permanent: RecordPopulation counts a record as in-shards from insert until removal, and being out at a parked worker does not remove it. Stage 1 produced 12 records against an out-for-processing target of 8 ("with a margin", said the comment), so records-in-shards sat at 12 against WorkManager.isSufficientlyLoaded()'s threshold of 8 for the rest of the run. BrokerPollSystem paused the data partition for back pressure, and nothing could resume it - both resumeIfPaused and maybeWakeupPoller are conditioned on !isSufficientlyLoaded(). The stage-2 records carrying offset 25 were therefore never fetched. It passed at all only by racing. Pausing takes effect at the top of a poll iteration, so a consumer.poll() already in flight when the gate closed went on delivering for up to the 2s long-poll timeout. Winning that race gave a ~14s pass; losing it gave the mute 30s timeout CI kept seeing on unrelated branches, including documentation-only heads. CONTROL ARMS, predictions stated before running. baseline (unchanged) PASS 15.6s A wait for isSufficientlyLoaded() before stage 2 3/3 PASS - PREDICTION REFUTED A' wait for isSubscriptionsPausedForBackPressure() 3/3 FAIL, 36.8-37.4s, exact CI signature B stage 1 produces 8 rather than 12 10/10 PASS, 13.6-14.3s C arm B, ProcessingShard stale replacement disabled FAIL on the 909 signature, 104s Arm A is worth reading: forcing the intake gate shut did NOT reproduce the failure, because the gate closing is not the same event as the partition being paused - only the second closes the window. The refuted arm is what located it. Arms A' and B differ by one record either side of the threshold, in the same position, and the outcome flips. Arm C is what says the repair did not cost the reproduction: it still goes red against the defect, on the right assertion, naming exactly the 25 keys k-25..k-49 that the original write-up recorded. THE FIX. STAGE_1_RECORDS is derived from MESSAGE_BUFFER_SIZE rather than written as a literal, so the two cannot drift; at exactly the target the comparison is strictly greater-than and the gate stays open. The precondition is asserted immediately before the stage-2 produce, naming the gate and its arithmetic - it was implicit, and the failure was mute, which is why eleven sightings over three weeks could establish only that it was master-state. The guard now reads the intake state AFTER its wait and reports it either way, including the poller's own pause observation. The class javadoc records this as a fourth precondition belonging to the staging rather than to the defect. The anonymous PCModule is now a named local class so the test can reach BrokerPollSystem through the module's protected seam for that diagnostic. REJECTED. Raising messageBufferSize to 12 (same knife-edge, bigger numbers, and it moves the scan-suppression arithmetic the reproduction depends on). Producing all 50 records up front (the control thread would park before any work is taken, so saturation could never be established). Loosening the 30s guard, which would have hidden the fact that the records could not arrive at all. SAME-DEFECT-CLASS SWEEP - the class is "a test that saturates PC's pipeline and then expects more records from the broker". One instance, the one fixed. Checked and dismissed: BrokerPollerBackpressureTest (the inverse - it asserts the pause, then releases the gate and waits for the resume BEFORE producing 10 more); RetriesTest and PartitionOrderProcessingTest (buffer overrides but no second-stage produce while workers are blocked); DrainCloseTest, DrainingMemberRebalanceIT, TransactionTimeoutsTest, TransactionalEagerProcessingIT and Rebalance857CommitSyncDeadlockProbeBase (latch plus multiple produces, but no pinned static load factor and no permanently-held surplus). @Quarantined and the docs/quarantined-tests.md entry are deleted together, per rule 3. The flake's row and its closed section leave docs/inflight/test-untracked-ci-flakes.md in the same change: the durable diagnosis is the solutions write-up, and docs/inflight/AGENTS.md says a closed item is removed with what outlives it migrated first, not rewritten into a FIXED narrative. A one-line pointer, the file's own convention for a closed entry, is what replaces it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q99GxTheRQL6d7TDt1bUum
…ack pressure had stopped fetching RegistrationRaceStaleResidentIT failed across CI for a fortnight on its own SETUP guard - "control thread must reach the mid-loop pause point (offset 25)" - never on the confluentinc#909 assertion it exists to make, so every failure was evidence about nothing. #440 quarantined it on a sighting ledger with no mechanism. This is the mechanism, and it is the test's staging, not PC. DIAGNOSIS. The test saturates the pipeline by gating the user function so nothing ever completes, which makes whatever stage 1 produced permanent: RecordPopulation counts a record as in-shards from insert until removal, and being out at a parked worker does not remove it. Stage 1 produced 12 records against an out-for-processing target of 8 ("with a margin", said the comment), so records-in-shards sat at 12 against WorkManager.isSufficientlyLoaded()'s threshold of 8 for the rest of the run. BrokerPollSystem paused the data partition for back pressure, and nothing could resume it - both resumeIfPaused and maybeWakeupPoller are conditioned on !isSufficientlyLoaded(). The stage-2 records carrying offset 25 were therefore never fetched. It passed at all only by racing. Pausing takes effect at the top of a poll iteration, so a consumer.poll() already in flight when the gate closed went on delivering for up to the 2s long-poll timeout. Winning that race gave a ~14s pass; losing it gave the mute 30s timeout CI kept seeing on unrelated branches, including documentation-only heads. CONTROL ARMS, predictions stated before running. baseline (unchanged) PASS 15.6s A wait for isSufficientlyLoaded() before stage 2 3/3 PASS - PREDICTION REFUTED A' wait for isSubscriptionsPausedForBackPressure() 3/3 FAIL, 36.8-37.4s, exact CI signature B stage 1 produces 8 rather than 12 10/10 PASS, 13.6-14.3s C arm B, ProcessingShard stale replacement disabled FAIL on the 909 signature, 104s Arm A is worth reading: forcing the intake gate shut did NOT reproduce the failure, because the gate closing is not the same event as the partition being paused - only the second closes the window. The refuted arm is what located it. Arms A' and B differ by one record either side of the threshold, in the same position, and the outcome flips. Arm C is what says the repair did not cost the reproduction: it still goes red against the defect, on the right assertion, naming exactly the 25 keys k-25..k-49 that the original write-up recorded. THE FIX. STAGE_1_RECORDS is derived from MESSAGE_BUFFER_SIZE rather than written as a literal, so the two cannot drift; at exactly the target the comparison is strictly greater-than and the gate stays open. The precondition is asserted immediately before the stage-2 produce, naming the gate and its arithmetic - it was implicit, and the failure was mute, which is why eleven sightings over three weeks could establish only that it was master-state. The guard now reads the intake state AFTER its wait and reports it either way, including the poller's own pause observation. The class javadoc records this as a fourth precondition belonging to the staging rather than to the defect. The anonymous PCModule is now a named local class so the test can reach BrokerPollSystem through the module's protected seam for that diagnostic. REJECTED. Raising messageBufferSize to 12 (same knife-edge, bigger numbers, and it moves the scan-suppression arithmetic the reproduction depends on). Producing all 50 records up front (the control thread would park before any work is taken, so saturation could never be established). Loosening the 30s guard, which would have hidden the fact that the records could not arrive at all. SAME-DEFECT-CLASS SWEEP - the class is "a test that saturates PC's pipeline and then expects more records from the broker". One instance, the one fixed. Checked and dismissed: BrokerPollerBackpressureTest (the inverse - it asserts the pause, then releases the gate and waits for the resume BEFORE producing 10 more); RetriesTest and PartitionOrderProcessingTest (buffer overrides but no second-stage produce while workers are blocked); DrainCloseTest, DrainingMemberRebalanceIT, TransactionTimeoutsTest, TransactionalEagerProcessingIT and Rebalance857CommitSyncDeadlockProbeBase (latch plus multiple produces, but no pinned static load factor and no permanently-held surplus). @Quarantined and the docs/quarantined-tests.md entry are deleted together, per rule 3. The flake's row and its closed section leave docs/inflight/test-untracked-ci-flakes.md in the same change: the durable diagnosis is the solutions write-up, and docs/inflight/AGENTS.md says a closed item is removed with what outlives it migrated first, not rewritten into a FIXED narrative. A one-line pointer, the file's own convention for a closed entry, is what replaces it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q99GxTheRQL6d7TDt1bUum
…ut, not by waiving them (#473) Clears the quarantine registry by fixing what its entries were about, not by waiving them. With the third entry fixed on master by #466 in the meantime, no @Quarantined annotation remains in the tree, and the release workflow's "no release while tests are quarantined" block clears for 0.6.0.0. RegistrationRaceStaleResidentIT was quarantined by #440 on a sighting ledger with no mechanism: it failed for a fortnight across unrelated branches, always on its own SETUP guard ("control thread must reach the mid-loop pause point (offset 25)"), never on the confluentinc#909 assertion it exists to make. The mechanism is the test's staging, not PC. The test saturates the pipeline by gating the user function so nothing ever completes, which makes whatever stage 1 produced permanent; stage 1 produced four records more than the out-for-processing target, so records in shards sat permanently over WorkManager.isSufficientlyLoaded()'s threshold, the poller paused the partition for back pressure, and neither resume path can fire while the gate is closed. The stage-2 records carrying the pause point were never fetched. It passed at all only by racing a consumer.poll() already in flight when the gate closed. Control arms, predictions stated before running: waiting on the load gate did NOT reproduce it (the gate closing is not the partition pausing - the refuted arm located the window); waiting on the pause reproduced the exact CI signature every time; producing exactly the target passes every time; and with stale replacement disabled the repaired test still goes red on the 909 signature, naming the same 25 keys the original write-up recorded. The fix derives the stage size from the buffer size so the two cannot drift, asserts the intake precondition before the second produce instead of leaving it mute, and reports the intake state after the wait so a future failure says which of "slow broker" and "records never fetched" it was. Rejected: a bigger buffer (same knife-edge), producing everything up front (saturation could never be established), and loosening the guard (which would have hidden that the records could not arrive at all). MultiInstanceRebalanceTest.largeNumberOfInstances leaves the registry for a different reason: nothing about it was repaired, because nothing is broken. #444 measured its residual failure as the Kafka consumer group protocol under the profile's churn rate, so its legitimate output is a pass rate, and a required check that fails one run in fifteen for a reason no change to PC can move gates nothing. All three capacity profiles now carry @tag("capacity"), excluded by the required Performance Tests lane and by the pom's default exclusion and the CI wrappers' copies of it, and still selected by the scheduled experiment runners - verified by what each lane actually selected, not by the flags looking right. The two profiles that had no scheduled runner get one on the same weekly cadence, sharing the iteration loop in bin/lib/chaos-experiment-common.sh so the classifiers cannot drift, and the workflow's collect step scans their tally directories. A latent bug the tag split would have introduced silently is fixed on the way: getAnnotation(Tag.class) returns null once @tag is repeated, which would have reclassified every capacity profile as a correctness gate. The deterministic correctness twin, scriptedChurnRoundsCompleteWithoutStall, is untouched and keeps those code paths gated. The residual-failures note, which said its own subject had been acted on, is migrated to docs/solutions/test-flakiness/ and deleted with every citation repointed; the solved flake leaves the open flake register for its own write-up. Not done, and named: the capacity rate is still a per-batch tally, not compared against a rolling median. Co-authored-by: Claude Fable 5.1 (1M context) <noreply@anthropic.com>
56 commits of master, none of which touched this PR's mechanism: master changed neither `.github/workflows/release.yml`, `bin/release-notes.py`, `bin/test-release-notes.sh` nor `.github/workflows/mutation-full-sweep.yml` since the branch's last master merge, so the renderer and the workflow step that calls it merge unchanged. Master's `release.yml` still carries the inline `awk`/`sed` converter this PR replaces. Master also changed six of the files this PR touches. Four auto-merged: `docs/releasing.md` and `bin/AGENTS.md` (#457's job batching, #442's integration sharding), `.gitignore` (#378, #440), and `docs/inflight/release-0.6.0.0.md` (#448, #476). Two conflicted. `docs/inflight/release-0600-blockers.md` - the #197 triage bullet, inside the `post-merge: checked` markers. This branch (2026-09-01, 2026-09-03) rewrote it in post-merge terms as "all four landed"; master's grooming sweep (#476, 2026-09-08, correcting on 2026-09-07) rewrote the same bullet to say the empty-release-body item **was already false when written** - #72 gave the workflow a `--notes-file` built from the `CHANGELOG.adoc` section on 2026-07-29, with `--generate-notes` only as a fallback. Master's correction is right and is the later decision, so it is kept; master's claim that MDC is still open is not - #205 merged 2026-08-27, which is why this branch corrected it, and that correction is kept. Resolved as one bullet carrying both later facts, and it now states what #199 actually does (replace the inline converter, and fail the release on a missing or unrenderable section) rather than the retired claim that it restores a body that was never absent. `docs/refactoring.md` - two unrelated new sections appended at the same point. Both kept: this branch's "`bin/release-notes.py` is Python in a Node-default `bin/`" and master's "JUnit tag resolution is implemented twice". No content from either side dropped. Neither `--ours` nor `--theirs` was used. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019XS64Xttx4vF5datYh7fmk
No issue - this is a CI infrastructure flake, tracked so far only as a sightings row on a branch.
Description
CI lanes have been dying about 11 seconds in, before Maven prints anything:
Four sightings, all 2026-09-03, in lanes with nothing in common but the bootstrap:
Chaos Pain Suiteon #410Lincheckon #426Lincheckandstatic: infer, same run, on #435static: inferpassed on a plain re-run of the same head, so it was never the code.Diagnosis
.gitignore's blanket*.jarrule swept up.mvn/wrapper/maven-wrapper.jar, which the Mavenwrapper intends to be checked in. Without it,
mvnw's "Extension to allow automatically downloadingthe maven-wrapper.jar" block refetches the jar from Maven Central on every fresh checkout - so every
job in every run makes that request - and it does so with
--quiet/--silent, discarding the filewith
rm -fif the fetch fails. A refused or truncated download therefore leaves no error text andno class, and the next line of
mvnwrunsjavaagainst a classpath that does not exist. Thefailure is silent by construction, which is why it reads as a broken repository rather than a
network blip.
The fix
Track the jar, and negate the ignore rule for that one path - the same idiom
.gitignorealreadyuses for the nested
CLAUDE.mdbridges. The bootstrap then reads from the tree instead of thenetwork, so the failing step no longer exists on any lane.
The tracked bytes are Maven Central's own: sha256
e63a53cfb9c4d291ebe3c2b0edacb7622bbc480326beaa5a0456e412f52f066a, byte-identical to a freshdownload of
maven-wrapper-3.2.0.jar, which is the versionwrapperUrlin.mvn/wrapper/maven-wrapper.propertiespins. The sha survives a clone unchanged - the repo has no.gitattributes, so nothing mangles it.Verification - a control arm, both halves
Run on a fresh worktree of
master, which starts with no jar exactly as a CI checkout does:wrapperUrlpointed at an unreachable host:./mvnw -vreproduced the CIfailure exactly - the same two lines, exit 1.
git cloneof this branch, same unreachablewrapperUrl:./mvnw -vprinted
Apache Maven 3.9.0and never consulted the URL, because the[ -r "$wrapperJarPath" ]guard short-circuits the whole download block.
Rejected:
distributionType=only-scriptThe other candidate durable fix. It also removes the jar, but it removes it by replacing both
mvnwand
mvnw.cmdwith a different upstream script, on lanes spanningubuntu-latest,macos-latestand the self-hosted
highcpurunner, plus everybin/*.shwrapper that shells out to./mvnw.That is a behavioural change to the build entrypoint of the whole repo to fix a fetch that a tracked
file deletes outright. It also leaves the distribution-zip download in place, which is the one
network call both designs share - so it does not even buy a smaller network surface.
Rejected: adding
wrapperSha256Sum, because it breaks macOSWorth recording, because it looks like free belt-and-braces and is not. Pinning the jar's sum in
maven-wrapper.propertieswould make a future download failure loud -mvnwvalidates the jar onevery run whether it downloaded it or not. It was implemented, measured, and backed out:
mvnwpreferssha256sumwhen present, and macOS Darwin 25 now ships/sbin/sha256sum(Darwin1.0) whose
-creads checksum lines only from a file, never stdin. Somvnw'secho "$sum $jar" | sha256sum -cprints a usage message and exits 1. With a correct jar and acorrect sum,
./mvnw -vfailed with "Failed to validate Maven wrapper SHA-256, your Maven wrappermight be compromised". The
shasum -a 256 -cfallback fails the same way and is never reachedanyway. That would trade a rare CI flake for a certain local one on every Mac, and this repo has a
macos-latestlane. Git already content-addresses the blob, so the sum adds nothing the checkoutdoes not guarantee.
Same-defect-class sweep
The class is a file the toolchain requires at runtime, silently excluded by a blanket
.gitignorerule, whose absence fails at run time rather than at commit time. Checked, in
.gitignore:*.jar- the wrapper jar was the only instance. Nothing else in the tree references arepo-relative jar (
grep -rn "maven-wrapper\|\.mvn/wrapper"returns onlymvnw, the propertiesfile and the ignore rule itself).
*.zip,*.tar.gz,*.war,*.nar,*.ear,*.rar- nobin/*.shor workflow references arepo-relative archive. None found.
*.class- would hide.mvn/wrapper/MavenWrapperDownloader.class, but neither it nor its.javasource is in the tree, and
mvnwguards that fallback withif [ -e "$javaSource" ]. It means arunner with neither
wgetnorcurlwould silently fetch nothing - moot once the jar is tracked.CLAUDE.mdplus four!negations - all four bridge files present in the tree are tracked, sono live instance. It stays a standing trap: a fifth nested bridge would be ignored in silence.
Also carried here: a quarantine
Scope beyond the wrapper jar, folded in deliberately rather than split, because this branch is
merging next and both open PRs were blocked on the same red.
RegistrationRaceStaleResidentIT.freshArrivalCollidingWithStaleShardResidentMustStillGetProcessedis quarantined. It times out on its own setup guard, not on its assertion: the awaited condition
is the control thread reaching the mid-loop pause point that saturates the pipeline, not the
confluentinc#909signature the test exists to prove, so a failure says nothing about the defect itreproduces. Passes complete in about 10s; every recorded failure sits at the 30s timeout.
Quarantined on a recorded ledger rather than a diagnosis, which rule 1 of the registry allows.
node bin/inflight.mjs codecov test RegistrationRaceStaleResidentITis that ledger and outlives a CIlog: failures land on unrelated branches minutes apart while siblings pass in the same window, and it
has failed on heads whose only content was a dependency bump and on heads whose only content was
documentation. That is what rules out PR-state.
flapping = true, so a pass is report-only. Unowned, because no diagnosis exists - the registry entryrecords what would produce one rather than leaving it to be rediscovered.
Checklist
.gitignore; no topic doc states anything this contradictsdocs/features/- N/A - CI infrastructure, no user-facing featuredocs/inflight/working note (pr-/branch-) started at the PR's first commit - N/A - nothing here thatghcannot show. The flake is fully resolved by this one commit, so there is no transient cross-branch state to track; the sightings are in this body and the diagnosis is in the commit. Deliberately not editingdocs/inflight/test-untracked-ci-flakes.md, which has divergent versions across many branchesce-simplifyandce-code-reviewlocally - N/A - two-file config change, one of them a binary from Maven Central;ce-simplifyhas no code to work on.bin/check-all.shpasses: 16 gates ok, 0 failed