Skip to content

Mesh Issues Analysis ​

Mesh Issues Analysis is a scheduled health report that reads data your sources have already collected β€” node telemetry, traceroutes, NeighborInfo, and (where enabled) packet-log receptions β€” and surfaces the routing and RF problems that data already shows. It does not probe the mesh to find them.

The passive guarantee ​

This feature sends zero packets. It never generates a traceroute, never polls a node, and never sends a message. Every finding comes from packets your node or your MQTT sources already received and stored. "Run analysis now" runs the same read β€” it is passive too.

This matters because LoRa is a shared, half-duplex medium: a feature that looks cheap on a desk with one node becomes mesh-wide congestion on a busy channel with 200. A health report earns the right to run on a schedule (24 hours by default) only by staying entirely off the air. See the Mesh Impact checklist for the reasoning this feature was built against.

How it works ​

Analysis is a global, cross-source batch job, structurally identical to Position Estimation: it runs on a schedule (not in realtime), pools data from every source you can read, and stores findings in one global table. MeshCore sources are excluded β€” meshcore_nodes has no role/airUtilTx equivalent for the rules below to evaluate.

Findings are grouped into three tiers, run in order:

  • Tier A β€” Node health. Reads nodes and telemetry directly: battery, uptime, airtime/channel utilization, and the position history of infrastructure-role nodes.
  • Tier B β€” RF adjacency graph. Builds a graph of which nodes can hear each other from traceroutes, NeighborInfo, and (where enabled) the MQTT/Meshtastic packet logs, then evaluates router clustering, redundancy, link asymmetry, and coverage over that graph.
  • Tier C β€” Node flags. Folds in flags other MeshMonitor services already compute (packet-rate, key-security, and clock-offset detectors) and adds one new check of its own: nodes broadcasting position or telemetry far more often than the mesh needs.

Each finding tracks its own lifecycle: new, reopened (closed, then seen again), updated (still open, evidence refreshed), or closed (stopped recurring). A finding is never deleted outright β€” see Dismissing and auto-close.

What each rule means ​

[official] marks a threshold sourced from Meshtastic's own ROUTER_LATE guidance. [MeshMonitor] marks MeshMonitor's own judgement β€” reasonable, but not an official number, and usually tunable (see Settings).

For a detailed explanation of every test ID β€” what it checks, why it matters, and what to do about it β€” see the Test Reference.

Tier A β€” Node health ​

RuleFires whenThresholdSeverity
A1 Deprecated roleNode's role is REPEATER or ROUTER_CLIENTβ€”warning
A2a Chatty nodeMean airUtilTx over 24 h exceeds the ceiling, β‰₯6 samples8% [official], tunablewarning
A2b Congested areaβ‰₯3 nodes in one ~5.5 km bin, mean channelUtilization over the ceiling25% [official], tunablewarning
A2b Congested node1–2 nodes in a bin exceed the ceiling β€” not enough neighbors to confirm area-wide congestion25% [official], tunableinfo
A3 Infra node on failing powerInfrastructure role, not powered (battery β‰ 101), and either β‰₯2 uptime resets in 7 days or battery <20%20% battery floor [MeshMonitor]warning (resets) / info (battery only)
A4 Mobile infra nodeInfrastructure role whose observed position span exceeds the mobility distance500 m [MeshMonitor], tunablewarning
A5 Cosplay routerROUTER role with isUnmessagable=false (firmware β‰₯2.5.0), or broadcasting telemetry with a median interval under 2 hours (β‰₯5 samples, needs the packet log)2 h median, 5 samples [MeshMonitor]info

Tier B β€” RF adjacency graph ​

RuleFires whenThresholdSeverity
B1 Router clusterβ‰₯2 ROUTER/REPEATER-role nodes are mutually audible and their non-cluster client neighbors overlap significantly (β‰₯90%)cluster size 2, 90% client overlap [MeshMonitor]warning (β‰₯2), critical (β‰₯4), info (inferred-only evidence)
B2 Redundant routerOne infra node's direct-neighbor set is β‰₯90% covered by another's, both with β‰₯3 neighbors90% overlap [MeshMonitor]warning
B3 Asymmetric linkDirectional mean SNR between two nodes differs by more than the ceiling, β‰₯3 samples per direction6 dB [MeshMonitor], tunablewarning (infra endpoint involved) / info
B4 Idle routerAn infra node is heard direct but carries <1% of its area's traceroute hops while a peer carries >10%1%/10% split [MeshMonitor]info always
B5 Load-bearing CLIENTA non-infra node appears as an intermediate hop in β‰₯10 traceroutes and β‰₯25% of its area's paths25% area share [MeshMonitor]warning (not fixed+powered) / info
B6 Hop horizonOver 50% of a node's deduped observed packets arrive with hopLimit=0, β‰₯20 packets50%/20 packets [MeshMonitor]info
B7 Coverage shadowAn MQTT-only node (never RF-heard) falls inside a nearby router's observed RF rangeβ€” (info only)info, default rule toggle

Tier C β€” Node flags ​

RuleFires whenThresholdSeverity
C1 Excessive packet rateisExcessivePackets flag is set (own detector, deduped across vantages)β€”warning
C1 Key security issueAny of low-entropy key, duplicate key, or key mismatch is flaggedβ€”warning
C1 Clock offsetisTimeOffsetIssue flag is set30 min, fixed by TIME_OFFSET_THRESHOLD_MINUTES (env)info
C2 Over-broadcastingA non-tracker node's deduped position or telemetry median inter-arrival falls under the floor, β‰₯6 samples300 s [MeshMonitor], tunablewarning (unpowered or under half the floor) / info

C2 exempts TRACKER, SENSOR, and TAK_TRACKER roles β€” those roles exist to report frequently by design, and the rule would otherwise fire on every correctly configured device of that type. A node with no known role is skipped rather than assumed innocent or guilty.

Where the thresholds come from ​

Only two numbers here are official Meshtastic guidance, both from the firmware's ROUTER_LATE documentation:

  • ChUtil (channel utilization) above 25% means an RF area is congested.
  • AirUtilTX above 7–8% means a single node is using too much of the channel. MeshMonitor uses 8% as the default ceiling.

Every other number β€” cluster sizes, overlap ratios, hop-share splits, sample minimums, window lengths β€” is MeshMonitor's own judgement, chosen to avoid false positives on a small or sparse mesh rather than derived from an official source. Five of them are exposed as tunable settings; the rest stay fixed in code, listed here for reference:

ConstantValueRule(s)
Chatty-node minimum samples6A2a
Utilization window24 hA2a, A2b
Congested-area minimum nodes3A2b
Geographic bin size~0.05Β° (~5.5 km)A2b
Battery-low floor20%A3
Battery minimum samples3A3
Uptime-reset minimum count2A3
Power window168 h (7 days)A3, A4
Minimum position precision17 bits (~305 m)A4
Unmessagable-aware firmware floor2.5.0A5
Telemetry-cadence median ceiling2 hA5
Telemetry-cadence minimum samples5A5
Directional-SNR minimum samples3 per directionB3
Gateway-direct minimum receptions3evidence class 3
Gateway cell size cap64 nodesevidence class 3
Gateway SNR sample cap25 per edgeevidence class 3
Redundant-router minimum neighbors3B2
Redundant-router overlap ratio90%B2
Idle-router minimum area paths20B4
Idle-router max hop share1%B4
Idle-router peer min hop share10%B4
Load-bearing minimum traceroutes10B5
Load-bearing minimum area share25%B5
Hop-horizon exhausted ratio50%B6
Hop-horizon minimum packets20B6
Coverage-shadow minimum range samples3B7
Coverage-shadow range cap25 kmB7
Router-cluster warning size2B1
Router-cluster critical size4B1
Router-cluster client-overlap ratio90%B1 (shared with B2)
Router-cluster minimum non-cluster neighbours3B1 (shared with B2)
Over-broadcast minimum samples6C2
Auto-close clean-run count3all rules
Evidence list cap25 entriesall list-shaped evidence

Reading the report ​

The report opens with summary tiles β€” one per issue type present, showing the count, worst severity, and how many findings are new or reopened since the last run. Click a tile to filter to that type. A filter bar (severity, tier, type, source, node-name search) and a "Show dismissed" toggle apply to everything below.

Two views share those filters:

  • By issue β€” one collapsible section per issue type, each a compact, sortable table with columns chosen for that rule (SNR per direction for asymmetric links, resets and battery floor for power findings, and so on). Sections stay collapsed until you expand them β€” nothing loads until you ask β€” except a section holding a critical finding, which opens itself. Click a row for the full evidence.
  • By node β€” findings grouped by node, worst first, with one badge per issue type. A node with four findings is one row here; this is the view to work from when deciding which node to fix. Findings not tied to a single node (clusters, links, congested areas) sit in a pinned "Mesh-wide" group at the top.

Each type section and node row carries a bulk-actions menu: dismiss or restore everything in that group at once, or mute the rule outright (see Muting a rule). Bulk actions only ever touch findings your account can see.

Clickable node references ​

Every place a node's name shows up in a finding, the By Node group headers, the member and "shared with" chips inside evidence, is a clickable link. Click one to jump straight to a DM thread with that node on its source's Messages tab. When the same node exists on more than one source, a small picker opens so you can choose which source to open the thread on. Cell contents that used to overflow the table into a horizontal scrollbar (the C1 Details column and the multi-source list, in particular) now wrap within their cell instead.

Acting on findings ​

The recommendations in this report never suggest ROUTER, and never say "promote". Meshtastic's own guidance is that ROUTER is a legacy role kept for compatibility, not a fix to reach for. Depending on the finding, the right move is usually one of:

  • CLIENT β€” the default for a mobile or non-infrastructure node. If a node shows up in a load-bearing or redundant-router finding but isn't fixed and powered, deploying another CLIENT nearby (not upgrading this one) is the fix.
  • CLIENT_BASE β€” for a node that is fixed and powered but not meant to route for others; the natural landing spot for a mobile-flagged infrastructure node (A4) or an over-committed CLIENT (B5) once it's confirmed stationary.
  • ROUTER_LATE β€” the modern replacement for ROUTER/ROUTER_CLIENT/REPEATER when a location genuinely needs a dedicated router. It routes with lower priority so it doesn't dominate a busy channel.

Some findings point to hardware or siting, not a role change at all β€” B3 (asymmetric link) usually means an antenna, feedline, or siting difference, and A3 (infra node on failing power) means checking the power budget.

Coverage and why a rule is silent ​

Not every rule can evaluate on every install. A coverage preface at the top of the report states, in plain language, what evidence a run actually had and which rules went quiet for lack of it:

  • B3, B6, and B7 lean on optional data. B3 (asymmetric link) needs traceroutes or MQTT-derived SNR samples; B6 (hop horizon) needs a packet log (Meshtastic or MQTT) enabled; B7's evidence gets stronger with an MQTT source enabled, though it can still fire without one. The preface tells you which of these are off, as a hint β€” never as an instruction to turn something on. Both packet logs are opt-in, per-source features with their own storage cost, and this report doesn't get to make that call for you.
  • B7 estimates range, it doesn't model terrain. A router's "observed range" is the farthest positioned direct neighbor it has actually heard, capped at 25 km. It has no idea about hills or buildings between two points β€” a hill on one bearing doesn't shrink the estimate on a clear bearing 90Β° away.
  • B6's hop delta is a lower bound. Firmware 2.7+ gives favourite-router hops zero cost, so a packet can travel further than its hopLimit decrements suggest. B6 can under-flag as a result β€” never over-flag.
  • C2's candidate gate is a mean, not the median it reports. Finding candidates cheaply for a mesh-wide scan requires a fast first pass; the final number shown is always a true median, but the initial gate uses a mean, which can miss a node that broadcasts in a tight burst and then goes silent for days. This is deliberately the safe direction to be wrong in.
  • A5's telemetry-cadence clause needs the packet log. The second "cosplay router" signal β€” a ROUTER broadcasting telemetry far more often than the 12-hour role default (median under 2 hours, at least 5 samples) β€” reads broadcast TELEMETRY_APP receptions from the packet log, which is opt-in and off by default. With the packet log disabled the clause reports itself as unavailable and A5 falls back to the isUnmessagable clause alone; it never fires on missing data.

Dismissing and auto-close ​

Dismissing a finding hides it for every user β€” it's administrative state, not a per-viewer preference β€” and is reversible at any time by restoring it. Dismissing does not stop a finding's lifecycle: if the underlying condition is still there on the next run, a dismissed finding still gets its evidence refreshed; only its visibility changes.

A finding that stops recurring auto-closes after a number of consecutive clean runs β€” the "Auto-close after" setting, default 3 (about 3 days at the default 24-hour cadence), clamped to 1–20. Disabling a tier or a rule doesn't delete its existing findings; they simply stop reappearing and auto-close the same honest way. If you set the analysis frequency much faster than 24 hours, the auto-close window shrinks along with it (a 1-hour cadence auto-closes in about 3 hours); much slower, and it lengthens correspondingly.

Muting a rule ​

Each rule (all 18 issue types, one row per rule, grouped by tier) has its own mute checkbox under Global Settings β†’ Mesh Issues Analysis β†’ Individual rules, alongside the coarser Tier A/B/C toggles. Uncheck one to stop detecting it without touching anything else.

Muting is not deleting. A muted rule contributes no new findings starting with the next run, but it does not delete or hide the findings it already raised β€” the report still lists them, with everything (dismiss, restore, evidence) working normally. They stop being re-detected, accumulate clean runs the same way a finding does when its underlying condition genuinely goes away, and auto-close after the "Auto-close after" run count (default 3 β€” see Dismissing and auto-close above). There is no separate "close muted findings now" action: the honest signal that a mute took effect is the same clean-run counter every other rule already uses.

Saving the mute list only writes settings β€” like every other control on this page, it never triggers a run and never resets the schedule's last-run timer.

If you decide a rule was worth keeping after all, re-check it. The next run picks it back up immediately; nothing needs to be restored, since findings that hadn't yet auto-closed were never touched.

Settings ​

Open Global Settings (the gear icon in the dashboard sidebar) β†’ Mesh Issues Analysis. The controls are global and require settings:write.

Schedule

SettingDefaultNotes
EnabledonTurns the whole scheduled job on or off.
Analysis frequency24 hoursHow often the batch job runs.
Lookback window168 hours (7 days)How far back telemetry/traceroute data is pooled.
Traceroute pair bucket6 hoursHow the traceroute corpus dedupes repeated observations of the same node pair before sampling.

Rules

SettingDefaultNotes
Tier AonNode health rules (A1–A5).
Tier BonRF adjacency graph rules (B1–B7).
Tier ConNode-flag rules (C1, C2).
Individual rulesall onBelow the tier toggles, one checkbox per rule (grouped by tier) mutes just that rule β€” see Muting a rule. Coverage shadow (B7) lives here now; it's the one rule observed firing at pathological volume (500+ findings) on a busy mesh.

Thresholds

SettingDefaultRangeProvenance
Airtime TX ceiling8%1–50%[official]
Channel utilization ceiling25%5–100%[official]
Mobile span500 m50–50,000 m[MeshMonitor]
Link SNR asymmetry6 dB1–30 dB[MeshMonitor]
Broadcast interval floor300 s30–3,600 s[MeshMonitor]
Router cluster max link30 km1–500 km[MeshMonitor], B1 distance guard
Auto-close after3 clean runs1–20[MeshMonitor]

Every value is clamped when read, not rejected on save: an out-of-range number you enter is silently pulled back to the nearer bound the next time the scheduler (or this settings page) reads it, so a bad value degrades to a bounded run instead of an unbounded one. Saving these settings never runs analysis and never resets the schedule's last-run timer β€” use Run analysis now for that.

A Run analysis now button triggers an immediate, still-passive run. If one is already in progress, a second click is rejected rather than queued. The last-run line below the controls shows when analysis last completed and how many findings it produced; after a container restart, this line is recovered from the last successful run's stored summary rather than going blank until the next scheduled run.

Overlap with the Security tab ​

C1's three findings β€” excessive packet rate, key security, and clock offset β€” surface the exact same node flags the Security tab already shows. That's intentional, not a bug: C1 doesn't add new detection, it puts those flags in the same health report as the routing and RF findings, with dismiss/restore and the same auto-close lifecycle every other finding gets. If you see a node flagged both places, they're the same underlying signal β€” dismissing it here doesn't clear it there, and vice versa, since the two features track state independently.

Permissions & API ​

Findings and status map to permitted source read access; the routes gate the same way resolvePermittedSourceIds does elsewhere in the app β€” a caller who can't read any source gets an empty/forbidden response rather than a 500. Dismissing and restoring, and the scheduled job's manual trigger, require the global settings:write permission, with a visibility check that returns 404 (not 403) for a finding whose contributing sources you can't read β€” so a settings:write holder with narrow source access can't dismiss, or even discover the existence of, a finding outside their permitted sources.

  • GET /api/analysis/mesh-issues β€” list findings for your permitted sources
  • GET /api/analysis/mesh-issues/status β€” scheduler status, resolved thresholds, and the last run's coverage summary
  • POST /api/analysis/mesh-issues/run-now β€” trigger a run immediately (settings:write)
  • POST /api/analysis/mesh-issues/:id/dismiss / /restore β€” hide/unhide a finding for everyone (settings:write, plus the visibility check above)
  • Settings are saved through POST /api/settings (settings:write)