Potential Bugs Backlog¶
A queue of incidental discoveries — suspected bugs, latent races, missing validations, stale comments — that were spotted while working on an unrelated task and deliberately not fixed in-line, per the Stay-On-Task Policy in .junie/AGENTS.md.
This file is a backlog, not a tracker. Promote an entry to a real issue (or fold it into a future task's Effective Issue) when it is ready to be worked on. Once an entry is resolved, delete it — this file does not maintain a resolved-bug archive.
What this file is — and is not¶
Yes: "I was doing X, I noticed Y looks broken, Y is not part of X, and I am walking away from Y. Recording it here so a future task can pick it up."
No — do not use this file for any of:
- Work you are doing or just finished as part of the current task. Use the
<UPDATE>checklist, thesubmitsummary, the commit message, andCHANGELOG.mdfor user-visible changes. - A diary of your own fix attempts, build outputs, packaging chains, or per-session follow-ups. If you opened the entry and resolved it in the same session, delete the entry — it never belonged here. Do not annotate it with
**Resolved:**/**Follow-up:**bullets. - Durable engineering lore or repro recipes →
LESSONS_LEARNED.md. - Roadmap items or deferred design → the relevant plan doc or an ADR.
- Session resumption state → your
<UPDATE>checklist ordocs/dev/scratch/CHECKLIST-<slug>.md. - Test failures or CI noise from the current change → fix them or escalate; not here.
A correct entry describes someone else's future problem that the current task is choosing not to solve. If you catch yourself writing a multi-paragraph resolution log on an entry you authored this session, that is the misuse signature — remove the entry instead.
How to add an entry¶
Append to the Open section below using the template. Keep entries short — one paragraph each. If a deeper analysis is warranted, link to a separate doc rather than inlining it here.
Entries in the Open section are ordered by priority (highest first): runtime crashes and safety/thread-safety hazards first, then correctness/maintainability and operator-facing config issues, then performance, and finally cosmetic / log-noise / test-noise findings. When adding a new entry, insert it at the position matching its severity rather than strictly by date.
Template¶
### YYYY-MM-DD — <short title>
- **Discovered during:** <issue ref / short task description>
- **Location:** `<path/to/File.java>` line <N> (or symbol name)
- **Symptom / hypothesis:** <one or two sentences>
- **Impact:** <user-visible effect, best guess>
- **Suggested next step:** <minimal investigation or fix sketch>
Open¶
2026-06-24 - ConfigParser.update() drops top-level scalar values on a version-mismatch migration¶
- Discovered during: investigation of "Shape for region default was invalid. Falling back to SQUARE." + "null vert" on a fresh run (this session). Primary fix was aligning the regions
MultiConfigParserversion with the bundledregions/default.yml(1.0->1.1) so the migration no longer fires on fresh installs; the underlyingupdate()defect was left in place. - Location:
rtp-core/src/main/java/io/github/dailystruggle/rtp/common/configuration/ConfigParser.java#update(lines ~994-1056), reached fromcheck(...)(lines ~789-815) whenever the on-disk file version differs from the parser's declared version. - Symptom / hypothesis: When
update()runs against a freshly extracted file (e.g. bundledregions/default.ymlatversion: "1.1"loaded by a parser declaring"1.0"), the merge pass lost the top-level scalarshape: "@config"/vert: "@config"keys - afterupdate()the reloaded parserdatahad noshape/vertentry at all (verified: region rawshapewasnullpre-fix,@configpost-fix). ThegetDefaultsFromJar()overlay + oldYaml scalar re-apply (lines 1018-1052) does not reliably preserve these keys. - Impact: Any region/world file whose on-disk version differs from the code-declared parser version silently loses its
@configshape/vert inheritance on load, forcing the SQUARE/null-vert fallback and breaking location generation until the file is regenerated. Latent for every future version bump of aMultiConfigParser-managed file. - Suggested next step: Add a focused test that loads a
version-mismatched region file through a realConfigParserand asserts every top-level scalar (including@configtokens) survivesupdate(); then fix the overlay so scalar keys present in the old file are always re-applied (the!oldYaml.isConfigurationSection(key)branch at line 1046 appears correct, so the loss likely happens earlier in the rename/reload sequence - instrumentrenameFiles()+ thecachedLookupreload).
2026-05-16 — economy-isolation Vault debit running on caller thread (real isolation breach)¶
- Discovered during: Phase-M1 Paper devstack smoke for the B → C gate in
docs/dev/scratch/CHECKLIST-metrics-and-multiserver.md(Paper 26.1.2, 23:01 transcript). - Location: the Vault economy adapter path exercised by
[RTP test/economy-isolation]— likelyplatforms/rtp-bukkit/rtp-bukkit-common/.../economy/(Vault wrapper); subcommand atrtp-plugin/.../bukkit/commands/test/EconomyIsolationTestCmd(or equivalent undercommands/test/). - Symptom / hypothesis: Live Paper run reports
debit ran on caller thread 'Craft Scheduler Thread - 2 - RTP'; Vault isolation breached (latency=49863us). Vault calls are expected to be dispatched via Global Region / Async Scheduler per Folia threading rules (mirrored on Paper for parity), but the debit is executing on the caller's async RTP scheduler thread instead of being hopped onto an isolated executor. - Impact: Vault implementations that touch non-thread-safe state (most of them) can be corrupted by RTP-initiated debits/credits; on Folia this would throw
ThreadAccessException. On Paper it is a latent data-race risk. Predates this session. - Suggested next step: Audit the Vault wrapper for an explicit
RTP.scheduler.runAsync/ global-region hop aroundeconomy.withdrawPlayer(...); if absent, add one and re-run[RTP test/economy-isolation]. Add a regression test that asserts the debit thread name differs from the caller's.
2026-05-24 - cross-server /rtp completes for a disconnected player (CANCELLED -> RESERVED -> COMPLETED resurrection)¶
- Discovered during: verification of a
lobby-a/rtp log trace (this session). The trace showedterminal: ... newState=CANCELLEDfire on disconnect, then ~2s latersupplier: ... CANCELLED(pos=0) -> RESERVED(pos=0), then ~2s after thatterminal: ... newState=COMPLETED. The user chose to defer the fix to bundle it with Phase 2 A2 (atomic-claim Lua); recording here so it is not lost. - Location:
platforms/rtp-proxy/rtp-proxy-common/src/main/java/io/github/dailystruggle/rtp/proxy/common/dispatch/DefaultRtpDispatcher.javaclaimAfterSelect/sendAfterClaim(the only pre-claimsender.isConnected(...)gate is at line 225, before snapshot read; nothing re-checks before the reservation claim or before the finalCOMPLETEDemission). Lobby-side silent acceptance:rtp-plugin/src/main/java/io/github/dailystruggle/rtp/bukkit/network/NetworkStatusCache.java#pollOncelines 253-263 (the supplier-transition log path only checkss.nonTerminal()on the new state, not onprev). - Symptom / hypothesis: Two distinct defects. (a) The proxy dispatcher does not re-check
sender.isConnected(...)between the early gate and the reservation-claim step, so a player who disconnects mid-dispatch still has a reservation token allocated and aCOMPLETEDemitted against them. ThePLAYER_DISCONNECTEDbranch atDefaultRtpDispatcher.java:429exists but only fires on the transfer step, not on the claim step. (b)NetworkStatusCache.pollOncelogs any terminal -> non-terminal transition as if it were normal forward progress (CANCELLED(pos=0) -> RESERVED(pos=0)) instead of treating it as a protocol violation, swallowing the only signal a lobby has that something is wrong upstream. Terminal states (CANCELLED/COMPLETED/FAILED) are one-way perQueueStatus.Stateline 59 andReqRtpNet015NetworkWaitlistTest:87. - Impact: Per-disconnect: one reservation token wasted, one backend teleport pipeline run for a ghost player (queue depth + heap pressure + MemoryTracker churn on the chosen backend), no user-visible misbehavior. Under load (many players disconnecting mid-roundtrip during a server restart, client crash storm, or proxy stutter), this scales linearly. The lobby silent-acceptance half will mask the same shape of defect introduced by future transports (Redis, SQL
LISTEN/NOTIFY). - Suggested next step: Bundle with Phase 2 A2 (atomic-claim Lua). In
DefaultRtpDispatcher.claimAfterSelect, add asender.isConnected(...)re-check immediately beforetransport.claim(...)mirroring the gate at line 225; on negative, emitCANCELLED(PLAYER_GONE)and returnDispatchOutcome.Failed(PLAYER_GONE)without touching the queue. Separately, inNetworkStatusCache.pollOnceadd a warn-level branch whenprev != null && !prev.nonTerminal() && s.nonTerminal()so terminal-state resurrections surface as a protocol-violation warning instead of a normalsupplier:line. Regression test alongsideReqRtpNet015NetworkWaitlistTestasserting a CANCELLED row never re-entersnonTerminal()via the supplier path.
2026-06-14 - claim *Checkers query third-party / Bukkit APIs from the async verification thread (Folia thread-safety caveat)¶
- Discovered during: claim-plugin audit follow-up (this session).
- Location:
rtp-plugin/src/main/java/io/github/dailystruggle/rtp/bukkit/tools/softdepends/claims/*Checker.javaregistered as sync verifiers viaClaimIntegrations; the verification chain executes on the async/region pipeline thread (GlobalRegionVerifiers.checkGlobalRegionVerifiers). - Symptom / hypothesis: Querying WorldGuard / GriefPrevention /
Bukkit.getWorld(...)and similar off the main thread relies on each third-party plugin being thread-safe; most claim plugins are not Folia-aware. These are CPU/lookup calls (not blocking chunk I/O), so REQ-RTP-S-005 is not violated, but a non-thread-safe lookup could race on Folia. - Impact: Potential thread-safety races on Folia with claim plugins that mutate shared state during a lookup. No observed failure; functionally correct on Paper/Spigot today.
- Suggested next step: Document the off-thread-lookup caveat in
EXTERNAL_HOOKS.md(verifier threading row) andADR-019; where a claim plugin exposes an async/region-safe query, prefer registering the checker viaaddGlobalRegionVerifierAsyncand hop to the appropriate scheduler.
2026-05-22 - locale TSV pipeline doubles backslash-heavy values on every round-trip¶
- Discovered during: CHECKLIST-multiconfig-menu step 14. The
menuInfoBadPointsLabelvalue inmessages.ymlhad grown to ~2 MB at HEAD - a single line of\\\\\\\\...\u2691 bad-points map. The TSV round-trip (locale-files-to-csv->locale-files-from-csv) re-escaped every\to\\on write, doubling the value's size each pass. This session's pipeline run pushed the value from 2 MB to 16.7 MB and broke snakeyaml's 3 MB document cap. The runaway seed value itself was replaced with a clean"#9D7CD8&l# bad-points map"in baseline and every locale; the underlying escape-doubling bug in the TSV scripts remains. - Location:
scripts/locale-files-to-csv.ps1andscripts/locale-files-from-csv.ps1- their YAML-value escape rule for backslashes is not idempotent. Any string value containing a literal backslash will double in length on every full pipeline round-trip. - Symptom / hypothesis: The from-csv writer is emitting
\->\\(correct YAML double-quoted-string escape) but the to-csv reader is treating the resulting\\as a literal\\(two chars) rather than collapsing it back to\on the next read. After N round-trips a value with K backslashes becomes K * 2^N chars long. ThemenuInfoBadPointsLabelvalue almost certainly started as a single escape sequence that grew unnoticed across ~22 prior pipeline runs. - Impact: Any future baseline string containing a literal
\(Windows paths in examples, regex patterns, etc.) will silently grow on every pipeline run. The 3 MB snakeyaml cap eventually hard-breaksLocaleParityTest. Current shipped values appear to be safe because no other key uses\. - Suggested next step: Decide whether the canonical TSV value column should hold (a) the YAML-encoded form (with
\\) or (b) the decoded form (with\); fix one of the two scripts so they round-trip identically. Add a regression test that runs the pipeline twice and asserts byte-equality of the locale tree between passes.
2026-06-14 - claim-integration integrations.yml reroll toggles are startup-only; /rtp reload does not (un)register verifiers¶
- Discovered during: claim-plugin audit follow-up (this session). High-severity items (WorldGuard inversion, legacy Factions removal) and the sync-verifier fail-safe (#4) were fixed; this reload-toggle finding was reported and deliberately left for a separate pass.
- Location:
rtp-plugin/src/main/java/io/github/dailystruggle/rtp/bukkit/tools/softdepends/claims/ClaimIntegrations.java(setup->registerVerifiers, called once at startup); theConfigs.onReloadhook only rebuilds theConfigParser. There is no per-verifier unregister path onGlobalRegionVerifiers(onlyclearGlobalRegionVerifiers()). - Symptom / hypothesis:
ClaimIntegrations.setupregisters each enabled claim verifier exactly once. Turning an integration off inintegrations.ymland running/rtp reloadleaves its verifier active; turning one on after startup never registers it. The live-reload hook implies the toggles are dynamic, but they are effectively restart-only. - Impact: Operator confusion / config that silently does not take effect until a full restart. No safety regression (a stale-active verifier still rejects claimed land; a never-registered one only fails to add protection the operator just enabled).
- Suggested next step: Either (a) document the
rerollXkeys as restart-only in theintegrations.ymlcomments and the docs, or (b) add a targeted unregister/re-register path. Option (b) is non-trivial: a blanketclearGlobalRegionVerifiers()+ re-register on reload would also drop any third-party verifiers registered viaRTPAPI.hooks().verifiers(), so it needs a token/handle-based remove API onGlobalRegionVerifiers(and theRegionVerifierRegistryfacade) so each*Checkerremoves only its own registration.
2026-05-24 - BukkitRTPWorld.getBiomes(world) / FoliaRTPWorld.getBiomes(world) return empty set when the platform setter is null-returning instead of falling back to the world enumeration¶
- Discovered during: /rtp biome=minecraft:BADLANDS namespace-parity fix (this session). User flagged the empty-set fallback explicitly.
- Location:
platforms/rtp-bukkit/rtp-bukkit-common/.../bukkitplatform/world/BukkitRTPWorld.javagetBiomes(RTPWorld<?>)(line ~117), and the mirroredplatforms/rtp-folia/rtp-folia-common/.../folia/world/FoliaRTPWorld.javagetBiomes(RTPWorld<?>)(line ~99). Both doSet<String> pre = getBiomes.apply(world); return (pre == null) ? new HashSet<>() : new HashSet<>(pre);. - Symptom / hypothesis: If a custom platform adapter (or an addon that called
setBiomesGetter) installs a getter that returnsnullfor some worlds, the public staticgetBiomes(world)silently returns an empty set rather than falling back to the platform's full biome enumeration (Biome.values()/Registry.BIOME). Tab-completion, the menu's biome picker, and any caller using this surface to validate user input would then treat every biome as unknown for that world. - Impact: Latent: today's default lambda never returns null, so the empty-set branch is unreachable in shipped configurations. The hazard surfaces only when a third-party setter (test fixture, addon, or future platform-specific override) returns null. The visible failure mode would be /rtp biome=
rejected for every x in the affected world, with no log line. - Suggested next step: Replace the
new HashSet<>()fallback with a call to the default platform enumeration (e.g. inline theBiome.values()/Registry.BIOMEwalk from the default lambda, or expose adefaultBiomes(world)helper next tosetBiomesGetterand route the null branch through it). Add a regression test that installs asetBiomesGetter(w -> null)lambda and assertsgetBiomes(w)is non-empty and contains a known vanilla biome.
2026-06-14 - claim *Checkers log "Critical architectural incompatibility" SEVERE on first absence/NPE rather than a benign not-initialized message¶
- Discovered during: claim-plugin audit follow-up (this session).
- Location:
rtp-plugin/src/main/java/io/github/dailystruggle/rtp/bukkit/tools/softdepends/claims/LandsChecker.java(isInClaimdereferenceslandsIntegrationwith no null check after a failedlandsSetup),GriefPreventionChecker.java(mixes the resolved-instance check with the staticGriefPrevention.instance.dataStore), and the shared SEVERE-on-first-Throwablelog pattern across the*Checkers. - Symptom / hypothesis: When a checker's setup silently failed (swallowed
NoClassDefFoundError/NPE), the first lookup NPEs and is reported as a "Critical architectural incompatibility" SEVERE line, rather than a benign "plugin not initialized" notice. - Impact: Misleading SEVERE log noise that looks like a serious defect to operators; the checker still fails-safe (disables itself), so no teleport-safety impact.
- Suggested next step: Add an explicit null/initialization guard before the first API call in each checker (especially
LandsChecker) and downgrade the "not initialized / plugin absent" path to a single INFO/FINE line, reserving SEVERE for a genuine unexpectedThrowablefrom a present, initialized plugin.
2026-06-14 - global Semaphore(1) serializes every per-attempt verification pass¶
- Discovered during: claim-plugin audit follow-up (this session).
- Location:
rtp-core/src/main/java/io/github/dailystruggle/rtp/common/selection/region/GlobalRegionVerifiers.java-regionVerifiersLock(Semaphore(1)) is held for the entire sync-verifier loop and while assembling the async chain incheckGlobalRegionVerifiers, on every candidate coordinate. - Symptom / hypothesis: Under a busy multi-region queue, every concurrent per-attempt verification serializes on the single permit, even though the common case is a read-only iteration of two small lists. This is a throughput bottleneck, not a correctness bug.
- Impact: Reduced verification throughput under concurrent load (multiple regions / large queues). No functional defect.
- Suggested next step: Replace the read-side lock with a copy-on-write snapshot of the two verifier lists (e.g.
CopyOnWriteArrayList, or volatile immutable-list references swapped on register/clear) socheckGlobalRegionVerifiersruns lock-free and only the rare register/clear mutations synchronize.
2026-06-14 - GriefPrevention / Lands claim checks use chunk-resolution granularity (conservative, lowers candidate yield)¶
- Discovered during: claim-plugin audit follow-up (this session).
- Location:
rtp-plugin/src/main/java/io/github/dailystruggle/rtp/bukkit/tools/softdepends/claims/GriefPreventionChecker.javaandLandsChecker.java- both test claim membership at chunk resolution (>> 4). - Symptom / hypothesis: A partially-claimed chunk is rejected wholesale, so unclaimed portions of that chunk are never offered as RTP destinations. This is conservative (never a false-accept, so REQ-RTP-S-003 stays safe) but reduces candidate yield.
- Impact: Slightly lower destination yield near claim borders; no safety impact.
- Suggested next step: Where the plugin API supports a per-block claim query (GriefPrevention
getClaimAtaccepts a precise location; Lands has a per-blockisClaimedoverload), switch to block-resolution testing to recover yield. Validate the precise-overload availability against the bumped Lands7.xAPI.
2026-06-14 - LandsChecker compiles against deprecated LandsIntegration constructor + isClaimed overload¶
- Discovered during: claim-plugin dependency refresh + audit follow-up (this session). The Lands artifact was bumped to
com.github.IncrediblePlugins:LandsAPI:7.19.1; the checker still uses the now-deprecated 6.x-style API surface. - Location:
rtp-plugin/src/main/java/io/github/dailystruggle/rtp/bukkit/tools/softdepends/claims/LandsChecker.java-LandsIntegrationconstructor and theisClaimedchunk overload (deprecation warnings only; compiles green). - Symptom / hypothesis: The deprecated ctor/overload could be removed in a future Lands major, breaking compilation at the next bump.
- Impact: None today (deprecation warning only). Future Lands API removal would break the
rtp-pluginbuild until the checker is rewritten to the current factory/query API. - Suggested next step: Rewrite
LandsCheckeragainst the currentLandsIntegration.of(plugin)factory and the non-deprecated land/area query API for 7.x, ideally with a precise (block-resolution) claim check (see the granularity entry above).
2026-05-23 - multi-config entry editor renders differently from built-in default (shape/vert stored as section vs FactoryValue)¶
- Discovered during: menu nested-config editing fix (this session, route A). Resolved the nested-row editability symptom (clickable dotted
OpenConfigKey+ dotted-param registration inSubConfigCmd) but left the visual/UX divergence between built-indefaultand runtime-added entries (e.g.default1234) untouched. - Location:
rtp-core/src/main/java/io/github/dailystruggle/rtp/common/configuration/MultiConfigParser.javaaddParser;rtp-core/src/main/java/io/github/dailystruggle/rtp/common/selection/region/RegionConfigLoader.java#load(the shape/vert factory-merge pass);rtp-core/src/main/java/io/github/dailystruggle/rtp/common/commands/menu/multiconfig/MultiConfigMenuBuilder.java#buildEntry. - Symptom / hypothesis: For built-in
default,parser.getData().get(RegionKeys.shape)returns aShapeFactoryValue<?>instance (set byRegionConfigLoader.loadduring the normal startup load), which renders as one clickableshape: <toString>row. For a freshly menu-addeddefault1234, the same getter returns the rawRtpYamlSectionfrom the seeded YAML becauseMultiConfigParser.addParserdoes NOT run the shape/vert factory-merge pass thatRegionConfigLoader.loaddoes, sobuildConfigFileflattens it into manyshape.radius/shape.centerXrows. Two visibly different editor styles for the same conceptual entry; only the rendering differs, not the underlying parser ability. - Impact: Cosmetic / UX inconsistency; admins see one editor for
defaultand a different (flattened, longer, more granular) editor for any added entry. Both editors now allow value edits after this session's fix, but the difference is jarring and the flattened form lacks the shape/vert type-picker entry-point thatdefaultexposes. - Suggested next step: On
MultiConfigParser.addParser(or in the menu's ADD branch atMenuRedeemSubcommand.dispatchMultiConfigMutate), after the new childConfigParseris created, run the same shape/vert factory-merge pass thatRegionConfigLoader.loadperforms so the storedshape/vertvalue is aFactoryValue<?>from the start. Then both built-in and runtime-added entries render through the same single-clickable-row branch. Separately, consider routing top-levelshape/vertclicks through the existingCommandTreeMenuBuilder.buildShapeVertTypePicker->buildShapeVertSubParamPagechain (already implemented at lines 969 / 1057) instead of opening an anvil for the whole shape param; this would make sub-param edits (radius) addressable without staging the entire shape value.
2026-05-20 - &c color code leaks into console on invalid command dispatch failure¶
- Discovered during:
/rtp test network allgrammar-fix landing (this session). The original symptom was the bare-token dispatch bug (invalid command - all), now fixed by promoting probe modes to real subcommands; but the operator-visible log line was double-emitted with a raw color code:&c[P0] invalid command - allfollowed by[RTP] invalid command - all. - Location: the
msgInvalidCommand/msgBadParameterpath on the Bukkit base command (likelyplatforms/rtp-bukkit/.../BukkitBaseRTPCmdor theSendMessage.logconsole branch). Format string presumably contains&c[P0]and is logged throughRTP.log(Level.WARNING, ...)without running it through theSendMessagecolor translator first. - Symptom / hypothesis: The
&clegacy color code is translated when sent to a player but logged verbatim when routed throughRTP.log->Bukkit.getConsoleSender().sendMessage(or similar). The double emission (one with&c[P0]prefix, one without) suggests two log paths fire for the same failure: one throughSendMessage.logand one through a plainRTP.log. - Impact: Cosmetic / log-noise. Operators see a raw
&cin their console on every dispatch failure, which both looks broken and makes log scraping forinvalid commandbrittle. The duplicate line also makes audit grep return double-counts. - Suggested next step: Inspect
BukkitBaseRTPCmd.msgInvalidCommand(and itsmsgBadParametersibling, REQ-RTP-S-004 auditing surface): either run the message throughChatColor.translateAlternateColorCodes('&', msg)beforeRTP.log, or drop the&c[P0]prefix from the format and rely on the log handler to colorize. Then de-dupe the dual log path so only one line emits.
2026-05-16 — disconnect-midflight probe emits WARNING-level [ENQUEUE_TRACE] ... DROPPED for synthetic UUID¶
- Discovered during: Phase-M1 Paper devstack smoke for the B → C gate in
docs/dev/scratch/CHECKLIST-metrics-and-multiserver.md(Paper 26.1.2, 23:01 transcript). - Location:
AbstractServerAccessor.sendMessage(Bukkit-family adapter) — the[ENQUEUE_TRACE]branch that fires whenBukkit.getPlayer(uuid)returns null. Probe origin:[RTP test/disconnect-midflight]subcommand underrtp-plugin/.../bukkit/commands/test/. - Symptom / hypothesis: The disconnect-midflight test deliberately uses a synthetic probe UUID with no online player, so the drop is expected; but it is logged at WARNING and counted by the new
FullAudit>= Level.WARNINGfilter, flipping the row to FAIL. - Impact: False FAIL in
/rtp test fullon every server (Paper devstack shows it cleanly); also pollutes the operator's WARN-level log on every synthetic probe. No runtime defect. - Suggested next step: Either (a) downgrade the
[ENQUEUE_TRACE] ... DROPPEDline toLevel.FINE(it is a diagnostic, not a user-impacting warning), or (b) have the disconnect-midflight probe register its synthetic UUID withSendMessageso the drop is silenced for that one ID. (a) is the cleaner fix.