Tests pin shared logger levels and never restore them: 9 files, 19 pins, one proven cross-class collision — share SessionManagerTest's CapturedLog #529

Closed
opened 2026-09-12 07:27:43 +02:00 by ltms · 1 comment
Owner

Follow-up to #525 (merged, PR #527). #525 fixed one file. This is the rest of the repo, and it adds the one thing #525 could not: a measured cross-class collision rather than a suspected one.

All numbers below measured on main at a6415f3.

The shape

A test pins a shared logger's level to capture output:

Logger logger = (Logger) LoggerFactory.getLogger(Something.class);
logger.setLevel(Level.WARN);
...
} finally {
    logger.detachAppender(appender);   // restores the appender, not the level
}

ch.qos.logback.classic.Logger instances are cached per class and shared for the whole JVM, and surefire reuses forks. So the level stays pinned for every test that runs afterwards — in that class, and in any other class in the same fork.

This is the sixth member of the vacuous-test family, defined by consequence (the suite stays green when the behaviour is absent): a test that leaves a shared instrument mis-set for every test after it. The test with the bug passes. The damage lands on a later test and looks like that test's bug.

The survey

Files where every setLevel call is an unrestored literal pin (a restore counts as setLevel(null) or setLevel(<captured variable>)):

Pins File
1 dev/ltms/fleet/FleetdAwaitHerdrTest.java
1 dev/ltms/fleet/FleetdReplyInboxSelectionTest.java
1 dev/ltms/fleet/auth/AuditLogTest.java
1 dev/ltms/fleet/inject/CompletionResolverTest.java
4 dev/ltms/fleet/inject/InjectorTest.java
1 dev/ltms/fleet/lead/LeadRolloverTest.java
1 dev/ltms/fleet/msg/AmqpConnectionFailureLoggerTest.java
8 dev/ltms/fleet/session/GitWorktreesTest.java
1 dev/ltms/fleet/session/WorktreeSessionManagerTest.java

9 files, 19 pins.

A number correction worth recording: an earlier, looser version of this survey said 15 files. That regex treated only setLevel(null) as a restore and so missed restores made through a captured variable. 9 is the number I stand behind. The script that produces it is in this ticket's history — re-run it rather than trusting the table, since #525 already removed SessionManagerTest from this list and the next fix will remove another.

The one proven collision

WorktreeSessionManagerTest.java:267-272 pins SessionManager's logger to Level.WARN in a finally that only calls detachAppender. That is the same logger SessionManagerTest uses.

Measured while verifying #525, running both classes in one fork with -Dsurefire.runOrder=reversealphabetical so WorktreeSessionManagerTest runs first, and with SessionManagerTest's @BeforeAll DEBUG baseline removed:

mvn exit=1
[INFO] Running dev.ltms.fleet.session.WorktreeSessionManagerTest
[INFO] Tests run: 24, Failures: 0 …
[ERROR] Tests run: 72, Failures: 3 … -- in dev.ltms.fleet.session.SessionManagerTest

AssertionFailedError: … expected: <DEBUG> but was: <WARN>
AssertionFailedError: both released sessions must be counted: no drain-complete INFO logged ==> expected: <true> but was: <false>
AssertionFailedError: both the ready and the busy session are released: no drain-complete INFO logged ==> expected: <true> but was: <false>

The WARN comes from WorktreeSessionManagerTest:272. Two of #512's drain assertions go vacuous under it — they assert an INFO line is present, and an INFO line cannot be emitted through a logger pinned at WARN, so assertTrue sees false.

What currently prevents this is SessionManagerTest's @BeforeAll DEBUG baseline, not anything in WorktreeSessionManagerTest. Nobody may remove that @BeforeAll as "only there for #525's proving test" — it is what keeps two other tests honest. I also measured the other half: removing #522's two per-test Level.INFO pins while keeping the @BeforeAll passes (24 + 72, 0 failures), so the per-test pin really is redundant and the baseline is the load-bearing part. PR #527's own comment has this backwards; #525's closing comment records the correction.

A second collision pair — shape confirmed, collision NOT run

FleetdAwaitHerdrTest:201-210 pins Fleetd.class to DEBUG and only detaches. FleetdReplyInboxSelectionTest:54-64 pins the same Fleetd.class logger to INFO and only detaches.

PR #527's survey described these as leaking "into each other". I checked, and that part is wrong: each file pins the level it needs at the start of its own capture, so each is immune to the other's leftover. The victim is a third party — any future test that asserts on Fleetd logging without pinning a level itself, which is exactly the trap #512 fell into. I have established the shape by reading both files; I have not run a collision for this pair, and I am not claiming one.

GitWorktreesTest is a third variation worth its own line: its @BeforeEach re-pins GitWorktrees.class to WARN before every test, so it self-heals within the class, but 7 tests re-pin INFO mid-test (lines 815, 830, 1438, 1455, 1475, 1498, 1686) and the @AfterEach only detaches. So the level this class leaves behind for the next class depends on which test happened to run last.

Asked for

  1. Promote CapturedLog to a shared test helper. #525 added it as a private static final class inside SessionManagerTest. Move it to a test-scope utility (suggestion: dev.ltms.fleet.testing.CapturedLog) unchanged in behaviour: at(Class, Level) captures the old level and pins the new one, of(Class) attaches without changing the level, and close() detaches the appender and restores the captured level. try-with-resources then makes "restored one, forgot the other" unwritable, because there is one thing to close.
  2. Convert all 19 pins in the 9 files above to it. Do not leave two ways to do this in the tree.
  3. Do not delete a test's own explicit pin as "now redundant." It is redundant for safety and load-bearing for intent — and, as measured above, the thing that looks redundant is sometimes not the thing that is.
  4. Report, do not fix, anything else that mutates shared static state without restoring it. One line each.

Acceptance

  • mvn -f fleetd/pom.xml clean install exits 0. Quote the Tests run / Failures / Errors / Skipped line verbatim. Do not pipe the command — a pipe hides a failure behind a zero exit. Redirect to a file and echo $? on its own line.
  • Re-run the survey script and show it reporting 0 files. That is the completion criterion, and it is cheap to check.
  • One proving test, ordered. Reproduce the collision above as a permanent test rather than a one-off measurement: assert that after WorktreeSessionManagerTest's dirty-worktree test has run, SessionManager's logger level is what it was before. Say how you forced the ordering, since cross-class ordering is not guaranteed by default — if you cannot make it deterministic, say so and assert within-class instead, and state plainly that the cross-class case is unproven.
  • Mutation gate, two mutations, both expected red. (a) Remove the level restore from the shared CapturedLog.close() and show the proving test go red, quoting the assertion and the exit code. (b) Separately, re-introduce the bare detachAppender-only finally in WorktreeSessionManagerTest and show the same. Prove each mutation applied with two greps using different search strings, each with a control against a pristine copy, then restore and confirm byte-identical with shasum -a 256, then a green control run.
  • Per fleet01's refinement, which I have adopted: a killed mutation needs no separate harness-proof cell, because the kill is itself proof the cell can go red. Only a surviving mutant needs one, before you write a word about what the survival means.

Related

  • #525 / PR #527 — the first file, and where CapturedLog comes from.
  • #512 / #522 — the two drain assertions that go vacuous under this leak.
  • #507 — the other shared-instrument member of the vacuous-test family.
Follow-up to #525 (merged, PR #527). #525 fixed one file. This is the rest of the repo, and it adds the one thing #525 could not: a **measured** cross-class collision rather than a suspected one. All numbers below measured on `main` at `a6415f3`. ## The shape A test pins a shared logger's level to capture output: ```java Logger logger = (Logger) LoggerFactory.getLogger(Something.class); logger.setLevel(Level.WARN); ... } finally { logger.detachAppender(appender); // restores the appender, not the level } ``` `ch.qos.logback.classic.Logger` instances are cached per class and shared for the whole JVM, and surefire reuses forks. So the level stays pinned for every test that runs afterwards — in that class, and in any other class in the same fork. This is the sixth member of the vacuous-test family, defined by consequence (*the suite stays green when the behaviour is absent*): **a test that leaves a shared instrument mis-set for every test after it.** The test with the bug passes. The damage lands on a later test and looks like that test's bug. ## The survey Files where **every** `setLevel` call is an unrestored literal pin (a restore counts as `setLevel(null)` or `setLevel(<captured variable>)`): | Pins | File | |---|---| | 1 | `dev/ltms/fleet/FleetdAwaitHerdrTest.java` | | 1 | `dev/ltms/fleet/FleetdReplyInboxSelectionTest.java` | | 1 | `dev/ltms/fleet/auth/AuditLogTest.java` | | 1 | `dev/ltms/fleet/inject/CompletionResolverTest.java` | | 4 | `dev/ltms/fleet/inject/InjectorTest.java` | | 1 | `dev/ltms/fleet/lead/LeadRolloverTest.java` | | 1 | `dev/ltms/fleet/msg/AmqpConnectionFailureLoggerTest.java` | | 8 | `dev/ltms/fleet/session/GitWorktreesTest.java` | | 1 | `dev/ltms/fleet/session/WorktreeSessionManagerTest.java` | **9 files, 19 pins.** A number correction worth recording: an earlier, looser version of this survey said 15 files. That regex treated only `setLevel(null)` as a restore and so missed restores made through a captured variable. 9 is the number I stand behind. The script that produces it is in this ticket's history — re-run it rather than trusting the table, since #525 already removed `SessionManagerTest` from this list and the next fix will remove another. ## The one proven collision `WorktreeSessionManagerTest.java:267-272` pins **`SessionManager`'s** logger to `Level.WARN` in a `finally` that only calls `detachAppender`. That is the same logger `SessionManagerTest` uses. Measured while verifying #525, running both classes in one fork with `-Dsurefire.runOrder=reversealphabetical` so `WorktreeSessionManagerTest` runs first, and with `SessionManagerTest`'s `@BeforeAll` DEBUG baseline removed: ``` mvn exit=1 [INFO] Running dev.ltms.fleet.session.WorktreeSessionManagerTest [INFO] Tests run: 24, Failures: 0 … [ERROR] Tests run: 72, Failures: 3 … -- in dev.ltms.fleet.session.SessionManagerTest AssertionFailedError: … expected: <DEBUG> but was: <WARN> AssertionFailedError: both released sessions must be counted: no drain-complete INFO logged ==> expected: <true> but was: <false> AssertionFailedError: both the ready and the busy session are released: no drain-complete INFO logged ==> expected: <true> but was: <false> ``` The `WARN` comes from `WorktreeSessionManagerTest:272`. Two of #512's drain assertions go vacuous under it — they assert an INFO line is present, and an INFO line cannot be emitted through a logger pinned at WARN, so `assertTrue` sees `false`. **What currently prevents this is `SessionManagerTest`'s `@BeforeAll` DEBUG baseline, not anything in `WorktreeSessionManagerTest`.** Nobody may remove that `@BeforeAll` as "only there for #525's proving test" — it is what keeps two other tests honest. I also measured the other half: removing #522's two per-test `Level.INFO` pins while keeping the `@BeforeAll` **passes** (24 + 72, 0 failures), so the per-test pin really is redundant and the baseline is the load-bearing part. PR #527's own comment has this backwards; #525's closing comment records the correction. ## A second collision pair — shape confirmed, collision NOT run `FleetdAwaitHerdrTest:201-210` pins `Fleetd.class` to `DEBUG` and only detaches. `FleetdReplyInboxSelectionTest:54-64` pins **the same** `Fleetd.class` logger to `INFO` and only detaches. PR #527's survey described these as leaking "into each other". I checked, and that part is wrong: each file pins the level it needs at the start of its own capture, so each is immune to the other's leftover. The victim is a **third** party — any future test that asserts on `Fleetd` logging without pinning a level itself, which is exactly the trap #512 fell into. I have established the shape by reading both files; I have not run a collision for this pair, and I am not claiming one. `GitWorktreesTest` is a third variation worth its own line: its `@BeforeEach` re-pins `GitWorktrees.class` to `WARN` before every test, so it self-heals *within* the class, but 7 tests re-pin `INFO` mid-test (lines 815, 830, 1438, 1455, 1475, 1498, 1686) and the `@AfterEach` only detaches. So the level this class leaves behind for the next class depends on which test happened to run last. ## Asked for 1. **Promote `CapturedLog` to a shared test helper.** #525 added it as a `private static final class` inside `SessionManagerTest`. Move it to a test-scope utility (suggestion: `dev.ltms.fleet.testing.CapturedLog`) unchanged in behaviour: `at(Class, Level)` captures the old level and pins the new one, `of(Class)` attaches without changing the level, and `close()` detaches the appender **and** restores the captured level. try-with-resources then makes "restored one, forgot the other" unwritable, because there is one thing to close. 2. **Convert all 19 pins in the 9 files above** to it. Do not leave two ways to do this in the tree. 3. **Do not delete a test's own explicit pin as "now redundant."** It is redundant for safety and load-bearing for intent — and, as measured above, the thing that looks redundant is sometimes not the thing that is. 4. **Report, do not fix, anything else that mutates shared static state without restoring it.** One line each. ## Acceptance - `mvn -f fleetd/pom.xml clean install` exits 0. Quote the `Tests run / Failures / Errors / Skipped` line verbatim. Do not pipe the command — a pipe hides a failure behind a zero exit. Redirect to a file and `echo $?` on its own line. - **Re-run the survey script and show it reporting 0 files.** That is the completion criterion, and it is cheap to check. - **One proving test, ordered.** Reproduce the collision above as a permanent test rather than a one-off measurement: assert that after `WorktreeSessionManagerTest`'s dirty-worktree test has run, `SessionManager`'s logger level is what it was before. Say how you forced the ordering, since cross-class ordering is not guaranteed by default — if you cannot make it deterministic, say so and assert within-class instead, and state plainly that the cross-class case is unproven. - **Mutation gate, two mutations, both expected red.** (a) Remove the level restore from the shared `CapturedLog.close()` and show the proving test go red, quoting the assertion and the exit code. (b) Separately, re-introduce the bare `detachAppender`-only `finally` in `WorktreeSessionManagerTest` and show the same. Prove each mutation applied with two greps using **different** search strings, each with a control against a pristine copy, then restore and confirm byte-identical with `shasum -a 256`, then a green control run. - Per fleet01's refinement, which I have adopted: a **killed** mutation needs no separate harness-proof cell, because the kill is itself proof the cell can go red. Only a **surviving** mutant needs one, before you write a word about what the survival means. ## Related - #525 / PR #527 — the first file, and where `CapturedLog` comes from. - #512 / #522 — the two drain assertions that go vacuous under this leak. - #507 — the other shared-instrument member of the vacuous-test family.
Author
Owner

Fixed by #533, merged to main as af95897. A javadoc follow-up went in as 7f8a882.

What I verified myself, in a scratch worktree at 8ea5c2b

Not promoted from the worker's report — re-run here.

  • mvn -f fleetd/pom.xml clean install exit 0, Tests run: 1701, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS.
  • Mutation (a), the shared instrument: removed logger.setLevel(originalLevel); from CapturedLog.close() → exit 1, Tests run: 25, Failures: 1, the one failure being sharedSessionManagerLoggerLevelIsRestoredAfterDirtyWorktreeReleasePinsWarn with expected: <TRACE> but was: <WARN>. Restored byte-identical.
  • Mutation (b), the use site: restored the pre-#525 hand-rolled ListAppender + setLevel + finally detachAppender pattern in releasePreservesDirtyWorktreeAndLogsWarn → exit 1, one failure, the same assertion. Restored byte-identical.
  • Green control on the restored tree: git diff --quiet clean, full suite exit 0, 1701/0/0/0.

Both mutants were killed, so neither needed a separate harness-proof cell — the kill is itself the proof the cell can go red. That economy came from the fleet01 lead and this ticket was the first to be written against it; it held up, and it saved two cells here.

Mutation (a) is the one that mattered most, and it is the reason this ticket existed. The instrument is now shared by ten files, so a silent regression in CapturedLog.close() would have degraded every one of them at once while each file's own tests stayed green. Mutating the subsystem could never have found that; only mutating the instrument does. That distinction also came from the fleet01 lead.

The survey, re-measured rather than taken from the report

On a6415f3, nine files carried 19 setLevel pins on a raw logback Logger with no restoring call. On the branch that set is empty.

One thing worth recording because it nearly read as a miss: GitWorktreesTest still shows seven setLevel calls afterwards. They are reportingLog.setLevel(...) on the CapturedLog instance, whose close() restores the original, so they are re-pins through the helper's own method and not leaks. A survey that greps \.setLevel( without looking at the receiver reports this file as still broken. Mine did, at first.

Three measurement errors of my own in this verification, all false zeros or false counts

Recording these because the ticket's whole subject is an instrument that lies:

  1. A for f in $files loop over a newline-separated list returned nothing useful — the shell here is zsh, which does not word-split an unquoted parameter. The loop body simply never ran on the right values.
  2. "$B:fleetd/..." lost its :f — zsh applied a history-style modifier to $B:f. The error surfaced as an "ambiguous argument" naming ...-8eetd/, which is only readable once you know what to look for.
  3. git grep -hE '^\s*@Test' returned 0 across every revision. \s is not valid in git grep's POSIX ERE. A plausible-looking zero that was entirely an artefact of the pattern.

All three are the zero-match family in my own hands, and the second and third produced numbers rather than errors, which is the dangerous form. The first unrestored column I printed was also mislabelled: it counted pinning calls, not unrestored ones, so every correctly-paired file appeared to have one leak. That number was plausible, which is exactly why it needed re-deriving rather than reading.

One number I had wrong and am retiring

I had been carrying 1699 as main's test count. Measured now: a6415f3 has 1695 @Test plus 2 parameterized/repeated, and the branch has 1696 plus 2. The delta is exactly +1, matching the per-file count (WorktreeSessionManagerTest 24 → 25). So the base is 1700, not 1699, and the worker's arithmetic was right where mine was wrong. No Java file differs between a6415f3 and main at 8335b12, so the base is the same on both.

The follow-up, 7f8a882

The new helper's javadoc said it is "the one way to pin or capture a logger's level and output in this test tree". That is false: nine files still hand-roll the pattern, with 42 raw setLevel calls between them (FleetdStartupReportTest, GitHostShapeReportTest, MemberCredentialsGapReportTest, MemberTrustModelReportTest, FleetHealthMonitorTest, ClaudeCodeLauncherTest, HerdrPeerLauncherAllowListWiringTest, HerdrPeerLauncherCharterTest, OpenCodeLauncherTest).

None of them is a defect — every one pairs its pin with a restore, so none is the #525 leak and none was in this ticket's scope, which was the unrestored pins only. The sentence was the problem, not the code: a reader who believes "the one way" and then greps finds nine counter-examples and cannot tell a leftover from a violation. Replaced with what is true, plus the two commands that re-measure it and an instruction to delete the paragraph once the first command comes back empty, rather than to keep a count current.

What this ticket did not close

Stated plainly rather than left to be assumed:

  • The cross-class case is not proven, by construction. @TestMethodOrder orders methods inside one class; it does not order classes. The exact #525 interleaving — WorktreeSessionManagerTest pinning WARN and bleeding into a later SessionManagerTest in the same fork — is fixed by the shared restoring helper, but no deterministic test asserts it. The worker flagged this itself rather than claiming more than it tested, and the javadoc on the proving test says so too.
  • Nine files remain unmigrated. Correct, but hand-rolled. Not filed as a ticket; the javadoc now carries the list and the re-measure command.
  • A separate appender leak, reported by the worker as its out-of-scope item 4 and verified here: FleetdLeadMailboxSelectionTest attaches three ListAppenders to the shared Fleetd logger and never detaches any. Filed as #535. Different family from this one — it pins no level, so it cannot corrupt a level assertion — and the genuinely useful part is the invariant that found it: a project-wide addAppender vs detachAppender balance count, 47 against 46, off by exactly one.

Closing.

Fixed by #533, merged to main as `af95897`. A javadoc follow-up went in as `7f8a882`. ## What I verified myself, in a scratch worktree at `8ea5c2b` Not promoted from the worker's report — re-run here. - `mvn -f fleetd/pom.xml clean install` exit 0, `Tests run: 1701, Failures: 0, Errors: 0, Skipped: 0`, BUILD SUCCESS. - Mutation (a), the shared instrument: removed `logger.setLevel(originalLevel);` from `CapturedLog.close()` → exit 1, `Tests run: 25, Failures: 1`, the one failure being `sharedSessionManagerLoggerLevelIsRestoredAfterDirtyWorktreeReleasePinsWarn` with `expected: <TRACE> but was: <WARN>`. Restored byte-identical. - Mutation (b), the use site: restored the pre-#525 hand-rolled `ListAppender` + `setLevel` + `finally detachAppender` pattern in `releasePreservesDirtyWorktreeAndLogsWarn` → exit 1, one failure, the same assertion. Restored byte-identical. - Green control on the restored tree: `git diff --quiet` clean, full suite exit 0, 1701/0/0/0. Both mutants were killed, so neither needed a separate harness-proof cell — the kill is itself the proof the cell can go red. That economy came from the fleet01 lead and this ticket was the first to be written against it; it held up, and it saved two cells here. Mutation (a) is the one that mattered most, and it is the reason this ticket existed. The instrument is now shared by ten files, so a silent regression in `CapturedLog.close()` would have degraded every one of them at once while each file's own tests stayed green. Mutating the subsystem could never have found that; only mutating the instrument does. That distinction also came from the fleet01 lead. ## The survey, re-measured rather than taken from the report On `a6415f3`, nine files carried 19 `setLevel` pins on a raw logback `Logger` with no restoring call. On the branch that set is empty. One thing worth recording because it nearly read as a miss: `GitWorktreesTest` still shows seven `setLevel` calls afterwards. They are `reportingLog.setLevel(...)` on the `CapturedLog` instance, whose `close()` restores the original, so they are re-pins through the helper's own method and not leaks. A survey that greps `\.setLevel(` without looking at the receiver reports this file as still broken. Mine did, at first. ## Three measurement errors of my own in this verification, all false zeros or false counts Recording these because the ticket's whole subject is an instrument that lies: 1. A `for f in $files` loop over a newline-separated list returned nothing useful — the shell here is zsh, which does not word-split an unquoted parameter. The loop body simply never ran on the right values. 2. `"$B:fleetd/..."` lost its `:f` — zsh applied a history-style modifier to `$B:f`. The error surfaced as an "ambiguous argument" naming `...-8eetd/`, which is only readable once you know what to look for. 3. `git grep -hE '^\s*@Test'` returned **0** across every revision. `\s` is not valid in git grep's POSIX ERE. A plausible-looking zero that was entirely an artefact of the pattern. All three are [the zero-match family](https://git.ltms.dev/fleet/fleetd/issues/) in my own hands, and the second and third produced numbers rather than errors, which is the dangerous form. The first `unrestored` column I printed was also mislabelled: it counted pinning calls, not unrestored ones, so every correctly-paired file appeared to have one leak. That number was plausible, which is exactly why it needed re-deriving rather than reading. ## One number I had wrong and am retiring I had been carrying `1699` as main's test count. Measured now: `a6415f3` has 1695 `@Test` plus 2 parameterized/repeated, and the branch has 1696 plus 2. The delta is exactly +1, matching the per-file count (`WorktreeSessionManagerTest` 24 → 25). So the base is 1700, not 1699, and the worker's arithmetic was right where mine was wrong. No Java file differs between `a6415f3` and main at `8335b12`, so the base is the same on both. ## The follow-up, `7f8a882` The new helper's javadoc said it is "the one way to pin or capture a logger's level and output in this test tree". That is false: nine files still hand-roll the pattern, with 42 raw `setLevel` calls between them (`FleetdStartupReportTest`, `GitHostShapeReportTest`, `MemberCredentialsGapReportTest`, `MemberTrustModelReportTest`, `FleetHealthMonitorTest`, `ClaudeCodeLauncherTest`, `HerdrPeerLauncherAllowListWiringTest`, `HerdrPeerLauncherCharterTest`, `OpenCodeLauncherTest`). None of them is a defect — every one pairs its pin with a restore, so none is the #525 leak and none was in this ticket's scope, which was the unrestored pins only. The sentence was the problem, not the code: a reader who believes "the one way" and then greps finds nine counter-examples and cannot tell a leftover from a violation. Replaced with what is true, plus the two commands that re-measure it and an instruction to delete the paragraph once the first command comes back empty, rather than to keep a count current. ## What this ticket did not close Stated plainly rather than left to be assumed: - **The cross-class case is not proven, by construction.** `@TestMethodOrder` orders methods inside one class; it does not order classes. The exact #525 interleaving — `WorktreeSessionManagerTest` pinning WARN and bleeding into a later `SessionManagerTest` in the same fork — is fixed by the shared restoring helper, but no deterministic test asserts it. The worker flagged this itself rather than claiming more than it tested, and the javadoc on the proving test says so too. - **Nine files remain unmigrated.** Correct, but hand-rolled. Not filed as a ticket; the javadoc now carries the list and the re-measure command. - **A separate appender leak**, reported by the worker as its out-of-scope item 4 and verified here: `FleetdLeadMailboxSelectionTest` attaches three `ListAppender`s to the shared `Fleetd` logger and never detaches any. Filed as #535. Different family from this one — it pins no level, so it cannot corrupt a level assertion — and the genuinely useful part is the invariant that found it: a project-wide `addAppender` vs `detachAppender` balance count, 47 against 46, off by exactly one. Closing.
ltms closed this issue 2026-09-12 08:00:33 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#529