fix: MeshCore low battery and inactive node notifications never fire#3672
Conversation
Both lowBatteryNotificationService and inactiveNodeNotificationService
iterate sourceManagerRegistry.getAllManagers() to find sources to check,
but MeshCore managers are registered exclusively in meshcoreManagerRegistry
and are never added to sourceManagerRegistry. As a result, no MeshCore
source was ever included in the notification scan — the voltage threshold
check and inactivity check simply never ran.
Fix: combine both registries when building the manager list in each
service, mapping MeshCoreManager instances to the minimal
{ sourceId, sourceType: 'meshcore' } shape the services need.
Update test fixtures to mock meshcoreManagerRegistry.list() for the
MeshCore test cases, reflecting the actual production code path.
Fixes: #3671
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lr9Jh3yPne1NVhnw4JmUNY
|
Claude finished @Yeraze's task —— View job PR ReviewTasks:
SummaryThis PR correctly identifies and fixes a real bug: The fix is clean, minimal, and well-tested. A few observations below. Code QualityDuplicate pattern in two services ( Both services now contain identical 5-line blocks to build the combined manager list: type MinimalManager = { sourceId: string; sourceType: string };
const managers: MinimalManager[] = [
...sourceManagerRegistry.getAllManagers(),
...meshcoreManagerRegistry.list().map(m => ({ sourceId: m.sourceId, sourceType: 'meshcore' as const })),
];This is fine for two callsites, but if a third notification service appears (e.g., a future "node offline" alert), the same block would need to be copied again. Consider extracting a shared helper (e.g., Type alias is re-declared in both files
Potential Issues
The combined array could theoretically contain the same
MeshCore nodes don't have a Meshtastic-style Test CoverageTest coverage is good and the structural change is important: MeshCore tests now set up managers via A few minor observations:
Security / PermissionsThe permission check ( Overall AssessmentApprove. The root cause is correctly identified, the fix is minimal and targeted, tests cover the production code path, and no regressions are introduced. The suggestions above are non-blocking improvements. |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lr9Jh3yPne1NVhnw4JmUNY
|
Claude finished @Yeraze's task —— View job PR ReviewTasks:
SummaryThis PR correctly identifies and fixes a real bug: The fix is clean, minimal, and well-tested. A few observations below. Code QualityDuplicate pattern in two services ( Both services now contain identical 5-line blocks to build the combined manager list: type MinimalManager = { sourceId: string; sourceType: string };
const managers: MinimalManager[] = [
...sourceManagerRegistry.getAllManagers(),
...meshcoreManagerRegistry.list().map(m => ({ sourceId: m.sourceId, sourceType: 'meshcore' as const })),
];This is fine for two callsites, but if a third notification service appears (e.g., a future "node offline" alert), the same block would need to be copied again. Consider extracting a shared helper (e.g., Type alias is re-declared in both files
Potential Issues
The combined array could theoretically contain the same
MeshCore nodes don't have a Meshtastic-style Test CoverageTest coverage is good and the structural change is important: MeshCore tests now set up managers via A few minor observations:
Security / PermissionsThe permission check ( Overall AssessmentApprove. The root cause is correctly identified, the fix is minimal and targeted, tests cover the production code path, and no regressions are introduced. The suggestions above are non-blocking improvements. |
The service uses databaseService.nodes.getInactiveMonitoredNodes (the current 4.x path); the top-level getInactiveMonitoredNodesAsync key in the mock was the old pre-4.x direct-method path and is never called. Addressed review feedback from PR #3672. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lr9Jh3yPne1NVhnw4JmUNY
|
Claude finished @Yeraze's task —— View job PR ReviewTasks:
SummaryThis PR correctly identifies and fixes a real bug: The fix is clean, minimal, and well-tested. A few observations below. Code QualityDuplicate pattern across two services ( Both services now contain identical 5-line blocks: type MinimalManager = { sourceId: string; sourceType: string };
const managers: MinimalManager[] = [
...sourceManagerRegistry.getAllManagers(),
...meshcoreManagerRegistry.list().map(m => ({ sourceId: m.sourceId, sourceType: 'meshcore' as const })),
];This is fine for two callsites, but if a third notification service is added, the pattern needs to be copied again. Consider extracting a shared helper (e.g.,
It's declared inside each private method. Moving it to module scope, or into a shared types file, would be cleaner but is not blocking. Potential Issues
MeshCore nodes don't have a Meshtastic-style Duplicate The combined array could theoretically contain the same Test CoverageTest coverage is good. Importantly, the MeshCore tests now use Minor observation — The variable Security / PermissionsThe Overall AssessmentApprove. Root cause is correctly identified, fix is minimal and targeted, tests cover the real production code path, and no regressions are introduced. All suggestions above are non-blocking. |
Summary
Fixes #3671 — MeshCore "Notify on Low Battery" never fires regardless of threshold configured.
Root cause: Both
lowBatteryNotificationServiceandinactiveNodeNotificationServicebuild their list of sources to scan by callingsourceManagerRegistry.getAllManagers(). However, MeshCore managers are registered exclusively inmeshcoreManagerRegistryand are never added tosourceManagerRegistry. This is even noted in a comment inmeshcoreManager.ts:As a result:
Changes
lowBatteryNotificationService.ts— combine both registries when building the manager list; mapMeshCoreManagerinstances to the minimal{ sourceId, sourceType: 'meshcore' }shape the loop needsinactiveNodeNotificationService.ts— same fixmeshcoreManagerRegistrymock; update the MeshCore-specific test cases to set managers viameshcoreManagerRegistry.list()(the actual production path) rather thansourceManagerRegistry.getAllManagers()Test plan
lowBatteryNotificationService.test.tspassinactiveNodeNotificationService.test.tspassmeshcoreManagerRegistry.list()→ adapter, matching production code path🤖 Generated with Claude Code
https://claude.ai/code/session_01Lr9Jh3yPne1NVhnw4JmUNY
Generated by Claude Code