Skip to content

A companion stores the contacts it learns but reports none: contacts3 holds them and every client is told "0 contacts in device" #725

Closed

Description

@A13xB0

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.

contacts on disk, none reported

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

  1. Fresh profile, open fixture-fife-strict, firmware.start, sim.play.
  2. console.type advert on a few repeaters; wait a few minutes.
  3. bench.serve any companion, meshcore-cli -t <host> -p <port> contacts → 0 contacts in device.
  4. 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

https://claude.ai/code/session_01RSb8bN9gtL72cXAxtyTbAG

Activity

  1. A13xB0 commented on Sep 8, 2026

    @A13xB0
    CollaboratorAuthor

    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_CONTACTS sizes ContactInfo contacts[MAX_CONTACTS+MAX_ANON_CONTACTS] inside the shared BaseChatMesh, with num_contacts declared immediately after it. The companion's MyMesh.h raises MAX_CONTACTS from the base default 32 to 100. On hardware platformio passes that to the whole build; the host build compiles each file separately, so BaseChatMesh.cpp kept 32 while MyMesh.cpp saw 100, and the BaseChatMesh subobject ended up with two layouts. num_contacts then had two offsets: addContact (in BaseChatMesh.cpp) wrote it at one, getNumContacts and the contacts iterator (inlined into MyMesh.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:

    [DBG] addContact type=2 transient=0 num 0->1
    [DBG] addContact type=2 transient=0 num 1->2
    [DBG] addContact type=2 transient=0 num 2->3      <- three loaded into contacts[]
    [DBG] GET_CONTACTS: totalSlots=8 getNumContacts=0 <- read at a different offset
    

    The three contacts are loaded from contacts3 at boot (an fopen/fread trace confirms every record is read), yet the count reads 0. Rebuilding the same binary with -DMAX_CONTACTS=100 gives count=3 and 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=100 in the companion's build flags, beside the -DMAX_GROUP_CHANNELS=40 that was already there for the same class of reason. The published companion binaries update when meshcore-native releases with it; MeshBench needs no change.

  2. A13xB0 commented on Sep 8, 2026

    @A13xB0
    CollaboratorAuthor

    Verified against the released artifact. Downloaded meshcore-companion_radio-linux-amd64 from the rebuilt companion-v1.17.1 release and drove it with a companion whose contacts3 holds three records: it reports CONTACTS_START count = 3 and 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.

  3. A13xB0 commented on Sep 9, 2026

    @A13xB0
    CollaboratorAuthor

    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.

    AngusOutlaw1   Markinch, SCO-ANG-01-RPT, Leslie 🏘, Blairgowrie_RPT   > 4 contacts in device
    Mothy          Markinch, Leslie 🏘, Blairgowrie_RPT                   > 3 contacts in device
    Jazzy          Markinch, Leslie 🏘, Blairgowrie_RPT                   > 3 contacts in device
    

    Both halves agree now: the workbench's own Companion panel lists them, and companion.state answers "contacts": 4 with contact: 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:

    UnicodeEncodeError: 'charmap' codec can't encode character '\U0001f3d8' in position 259
    

    That is 🏘, from Leslie 🏘. meshcore-cli had 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-8 is 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions