Repository navigation
A companion stores the contacts it learns but reports none: contacts3 holds them and every client is told "0 contacts in device" #725
Description
Found during the #318 pass on v0.0.11-dev.2, Windows 10 22H2, bundled MSI, clean install and fresh profile.
#712 is fixed — every node now has its own identity, and I have verified that first because it is what made this one invisible:
$ find . -name _main.id | wc -l 56
$ find . -name _main.id -exec sha256sum {} \; | awk '{print $1}' | sort -u | wc -l
56
56 nodes, 56 distinct keys. Thank you — that one was the important half.
What remains is the symptom I attached to it, and it has a different cause. A companion learns contacts, writes them to its own store, and reports none to any client.
What the node knows
After driving adverts from five repeaters for six minutes, each companion's contacts3 holds real, correct entries — distinct keys, right names, and they are the nodes I actually adverted from:
Mothy/contacts3 (304 bytes) key 3cd64e6b6d9e1af2… name 'Markinch'
Jazzy/contacts3 (304 bytes) key 3cd64e6b6d9e1af2… name 'Markinch'
PeterB/contacts3 (304 bytes) key 3cd64e6b6d9e1af2… name 'Markinch'
AngusOutlaw1/contacts3 (608 bytes) key dec9b87c081e4f82… name 'SCO-ANG-02-RPT'
key 5a5ed3242a25a7ed… name 'Blairgowrie_RPT'
So the firmware is doing its job: it heard the adverts, matched them against its own key, found them to be other people, and stored them.
What every client is told
AngusOutlaw1 heard=337 -> meshcore-cli contacts -> "0 contacts in device"
Mothy heard=169 -> meshcore-cli contacts -> "0 contacts in device"
Jazzy heard=136 -> meshcore-cli contacts -> "0 contacts in device"
This is not the client's fault. The workbench's own Companion panel says the same thing about the same nodes, in the screenshot above:
Mothy · 5ae4…0702 CONTACTS none yet - a companion learns these from adverts
AngusOutlaw1 · 1124…9f30 CONTACTS none yet - a companion learns these from adverts
Two different keys, so #712 is genuinely fixed — and two nodes whose contacts3 files, on disk, at that moment, hold the entries the panel says do not exist. The refresh button does not change it.
Why this is worth its own issue
The panel's sentence is the trap: "none yet - a companion learns these from adverts" tells a reader the mesh has not adverted yet, when the truth is that it has, the node recorded it, and the read path is what fails. On the last pass this looked like a consequence of the shared key, and it was reasonable to expect it to go with it. It has not.
It costs the same things #712 did, minus the cryptography: contacts, direct messages and anything addressed to a peer are unreachable from a companion client, and the companion bench cannot be used to develop the part of an app that lists people.
Repro
- Fresh profile, open
fixture-fife-strict,firmware.start,sim.play. console.typeadverton a few repeaters; wait a few minutes.bench.serveany companion,meshcore-cli -t <host> -p <port> contacts→0 contacts in device.xxd %LOCALAPPDATA%\meshbench\nodefs\<that node>\contacts3→ the contacts are there, with names.
Worth saying the rest of the companion bench is in good shape: infos answers correctly with the node's own key, bench.drop and reconnect both work, and the port-reclaim message is a model of a clear one.
Filed from the #318 pass. 🤖 Generated with Claude Code

Activity
A13xB0 commentedon Sep 8, 2026
Root-caused and fixed in the firmware, not MeshBench. The workbench is faithfully relaying what the companion reports.
It is a One Definition Rule violation in the native companion binary.
MAX_CONTACTSsizesContactInfo contacts[MAX_CONTACTS+MAX_ANON_CONTACTS]inside the sharedBaseChatMesh, withnum_contactsdeclared immediately after it. The companion'sMyMesh.hraisesMAX_CONTACTSfrom the base default 32 to 100. On hardware platformio passes that to the whole build; the host build compiles each file separately, soBaseChatMesh.cppkept 32 whileMyMesh.cppsaw 100, and theBaseChatMeshsubobject ended up with two layouts.num_contactsthen had two offsets:addContact(inBaseChatMesh.cpp) wrote it at one,getNumContactsand the contacts iterator (inlined intoMyMesh.cpp) read it at another.Measured with a debug print in a host companion built at
companion-v1.17.1, driving the raw bridge protocol with no MeshBench involved:The three contacts are loaded from
contacts3at boot (anfopen/freadtrace confirms every record is read), yet the count reads 0. Rebuilding the same binary with-DMAX_CONTACTS=100givescount=3and yields all three.Your observation that #712 made this visible is exactly right: while every node shared one identity, every advert was discarded as self, so the list was empty for a different reason and this was masked.
Fix: MeshBench/meshcore-native#22 pins
-DMAX_CONTACTS=100in the companion's build flags, beside the-DMAX_GROUP_CHANNELS=40that was already there for the same class of reason. The published companion binaries update when meshcore-native releases with it; MeshBench needs no change.companion: pin MAX_CONTACTS so every unit agrees on the contact table (…
A13xB0 commentedon Sep 8, 2026
Verified against the released artifact. Downloaded
meshcore-companion_radio-linux-amd64from the rebuiltcompanion-v1.17.1release and drove it with a companion whosecontacts3holds three records: it reportsCONTACTS_START count = 3and yields all three, where the previous binary reported 0. All the native companion releases (v1.10.0 through v1.17.1, plus main and dev) were rebuilt with the fix and their assets replaced.A13xB0 commentedon Sep 9, 2026
Verified fixed on v0.0.11-dev.4, Windows, clean install and fresh profile — and on dev.3 before it. The One Definition Rule diagnosis was exactly right.
Both halves agree now: the workbench's own Companion panel lists them, and
companion.stateanswers"contacts": 4withcontact: Markinch,contact: Blairgowrie_RPT… in its recent log. The rebuilt binary came down as expected on a fresh profile (companion-v1.17.1, 1,267,725 bytes against the old 1,259,224).One trap worth recording for the next person checking this on Windows, because it cost me three runs and looked exactly like the bug still being present:
That is
🏘, fromLeslie 🏘.meshcore-clihad the contacts and died printing them, on the Windows console's cp1252 default. A fixed build and a broken one look identical unless you read the exception rather than the exit status, and this fixture's node names make it certain to happen.PYTHONIOENCODING=utf-8is the workaround. Not MeshBench's bug — but if anyone adds a Windows check for this, that is the shape of the false negative.Closing, since it is fixed on two consecutive releases.
From the #318 pass on v0.0.11-dev.4. 🤖 Generated with Claude Code