Repository navigation
Active observers vanish as nodes after retention.nodeDays without an advert; observer link dead-ends on 'Node not found' #199
Description
Summary
An observer that is online can vanish as a node.
- The node row is moved to
inactive_nodesafterretention.nodeDayswithout an advert, even though the same device is actively uploading as an observer. - The Observers page still lists it as online. Its "View node detail" link (
public/observer-detail.jsaround line 84) then opens#/nodes/<pubkey>, which shows "Node not found" (public/nodes.jsaround line 1136). - The node is also missing from the map with every status filter.
Evidence
Production, read-only, 2026-10-04, reported by users:
- Observer: a repeater observer in one region. Its
observers.last_seenis current (2026-10-04T04:23Z), and it was first seen 2026-09-11. nodes: no row for its pubkey./api/nodes/<pubkey>returns 404.inactive_nodes: has the row, withlast_seen2026-09-24T15:35Z.- Adverts: its last advert in
transmissionsis from 2026-09-23T06:33Z. - Config:
retention.nodeDaysis 7.MoveStaleNodes(cmd/ingestor/db.goaround line 1900, called frommain.goat start-up and daily) moves every node whoselast_seen < now - nodeDays.last_seenis updated only by adverts and direct upserts. Observer activity does not count.
This is not a regression. The behaviour predates v0.2.0.
Why adverts can be missing. A node never receives its own transmissions, so its adverts must be heard by another observer. The firmware's flood.advert.interval can be set from 3 to 168 hours, with a default of 12 hours for repeaters. At 168 hours, a node with nodeDays: 7 flickers in and out of nodes. A badly placed node may also simply not reach any other observer for a week.
Proposed changes
A. Ingestor: do not retire active observers. In MoveStaleNodes, exclude nodes whose pubkey matches an observer seen within nodeDays. Observer IDs are upper-case pubkeys and node keys are lower-case, so the comparison must normalise case.
- When the observer goes quiet, the node is retired as before.
- This needs a test: an active observer whose node has an old
last_seenstays innodes, and an inactive one is moved. last_seenmust not be faked. The node page should still show when the last advert was heard.
B. UI: do not dead-end on "Node not found".
- Option 1: when the node is missing but present in
inactive_nodes, the node page says so, e.g. "No advert heard since ; this device is inactive", and shows the inactive row's name and role. - Option 2: at minimum, the observer-detail page does not offer "View node detail" for a pubkey that has no
nodesrow, or it explains why.
cmd/server/identity_visibility.go already reads inactive_nodes, so there is a read-only precedent in the server.
Acceptance criteria
- An online observer whose last advert is older than
nodeDaysstays visible as a node, on the map and on the node page (test incmd/ingestor). - A node that is in neither
nodesnor the active observer set is retired as before (test). - From the Observers page, a device without a
nodesrow never dead-ends on a bare "Node not found". Either the page explains the state or the link is hidden (unit or E2E test). - No new per-item API calls, and the server stays read-only.
Out of scope
- Changing
retention.nodeDayson any instance; that is an operational decision. - The map status filter. It defaults to "All" and only persists the user's own last choice in
localStorage(meshcore-map-status-filter).
Activity
Merge pull request #203 from dborup/codex/issue-199-observer-node-ret…
dborup commentedon Oct 4, 2026
Fixed by #203 (merged as
caee53f8):MoveStaleNodesno longer retires nodes whose pubkey matches an observer seen withinnodeDays. The comparison ignores case.Nodes already in
inactive_nodesreturn with their next advert. Review follow-ups (P3) are tracked in #208.