Skip to content

fix(meshcore): buffer GRP_TXT path so {ROUTE}/{SNR} resolve on routed channel messages (#3710)#3737

Merged
Yeraze merged 2 commits into
mainfrom
fix/meshcore-route-channel-grptxt-3710
Jun 25, 2026
Merged

fix(meshcore): buffer GRP_TXT path so {ROUTE}/{SNR} resolve on routed channel messages (#3710)#3737
Yeraze merged 2 commits into
mainfrom
fix/meshcore-route-channel-grptxt-3710

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Reopens/continues #3710 — the prior fix (#3715) did not fully resolve it.

Why #3715 wasn't enough

#3715 fixed the hop-count correlation so {ROUTE}/{SNR} resolved on routed DMs. But users kept reporting on hashtag/private channel messages — and the tell was "DMs consistent, channels hit or miss" (issue comments, still reproducing on v4.11.6).

Root cause

The LogRxData handler buffers the relay-hash chain + SNR only for TXT_MSG (0x02) packets:

if (pkt.payload_type === TXT_MSG) {
  this.pendingTxtMsgPath = { hops, rawPathLen: pkt.pathLen, snr, bufferedAt: ... };
}

DMs are TXT_MSG, but channel/group messages ride GRP_TXT (0x05) and surface as ChannelMsgRecv. Their path was never buffered, so consumePendingPath() returned nothing and {ROUTE}/{SNR} fell through to . The intermittent "hit" was a coincidental stale TXT_MSG buffer (from a recent DM) whose hop count happened to match — hence "hit or miss" on busy channels, consistent on DMs.

This also explains why both hashtag and private channels failed identically: both are group messages on the wire.

Fix

Buffer the path for GRP_TXT as well as TXT_MSG. ChannelMsgRecv carries a plain hop count (pathLen), so the existing freshness + hop-count correlation guards (#3589 / #3710) apply unchanged. GRP_DATA (0x06) is deliberately excluded — it isn't a text message and emits no ChannelMsgRecv.

Tests

491 MeshCore backend/manager tests pass (success=true); tsc clean for the changed file.

🤖 Generated with Claude Code

… channel messages (#3710)

The #3710 fix (correlating the LogRxData path by hop count) made {ROUTE}/{SNR}
work on routed DMs, but users still saw "—" on hashtag/private channel
messages — DMs consistent, channels "hit or miss".

Root cause: the LogRxData handler buffers the relay-hash chain + SNR only for
TXT_MSG (0x02) packets. DMs are TXT_MSG, but channel/group messages ride
GRP_TXT (0x05) and surface as ChannelMsgRecv. Their path was therefore never
buffered, so consumePendingPath() found nothing and {ROUTE}/{SNR} fell through.
The intermittent "hit" was a coincidental stale TXT_MSG buffer (from a recent
DM) whose hop count happened to match the channel message's.

Fix: buffer the path for GRP_TXT as well as TXT_MSG. ChannelMsgRecv carries a
plain hop count (pathLen), so the existing freshness + hop-count correlation
guards (issue #3589/#3710) apply unchanged. GRP_DATA (0x06) is deliberately
excluded — it isn't a text message and emits no ChannelMsgRecv.

Tests: the two existing channel_message tests built a TXT_MSG (0x02) packet then
fired ChannelMsgRecv, so a stale-buffer match masked the bug — corrected to
GRP_TXT (0x05) so they are real regression guards. Added a dedicated #3710 test
asserting a routed GRP_TXT channel message attaches its path + SNR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review

  • Read changed files
  • Review code quality and correctness
  • Evaluate test coverage
  • Post final review

Summary

This is a clean, well-scoped bugfix. The root cause diagnosis is correct and the fix is minimal.

Code Review: meshcoreNativeBackend.ts

Fix (line 453):

if (pkt.payload_type === TXT_MSG || pkt.payload_type === GRP_TXT) {
  this.pendingTxtMsgPath = { hops, rawPathLen: pkt.pathLen, snr, bufferedAt: Date.now() };
}

Correctness: The fix is correct. GRP_TXT (0x05) packets surface as ChannelMsgRecv, which already calls consumePendingPath(msg.pathLen) at line 540. All the existing guards (freshness check, hop-count correlation) apply identically — no special-casing needed for channel messages.

GRP_DATA exclusion is justified: The PR correctly excludes GRP_DATA (0x06) since it doesn't emit ChannelMsgRecv. The comment at lines 429–432 explains this clearly.

The variable GRP_TXT is cleanly scoped — declared at line 432 inside the if (typeof PushCodes?.LogRxData === 'number' && this.PacketCtor) block, consistent with how TXT_MSG was already handled.

No issues with the implementation. The single-slot buffer semantics and consume-once behavior are preserved.

Code Review: meshcoreNativeBackend.otaPacket.test.ts

The test corrections are important. Two existing tests (carries the buffered LogRxData SNR onto a channel_message event at line 168, and attaches the buffered path for a 2-byte-hash flood channel_message at line 362) previously used TXT_MSG (0x02) raw bytes for ChannelMsgRecv scenarios. They now use GRP_TXT (0x05), making them actual regression guards for this bug.

New regression test (line 186): The buffers a GRP_TXT (channel) path so {ROUTE}/{SNR} resolve on routed channel messages (issue #3710) test correctly uses packed pathLen 0x42 (2-byte hashes, 2 hops) and verifies both path_hops and snr are attached. It would fail without the fix.

Coverage gap — minor: No test covers a GRP_TXT packet with a direct (non-routed) path (pathLen=0xff). Given the fix is a simple || extension of the buffering condition, this is low risk, but a test verifying the pathLen=0xff → empty hops case for channel messages would complete the matrix. Not a blocker.

One observation on the test header comment (line 3–10):

"still buffer the relay-hash chain for TXT_MSG packets so the following ContactMsgRecv event can attach it."

This is now slightly stale — the comment should mention GRP_TXT / ChannelMsgRecv as well. Again, not a blocker.

Performance

No concerns. The change is a single boolean OR in an event handler hot path, with the same branching cost as before.

Security

No concerns. This doesn't change the trust boundary or expose any new data; it merely populates existing fields that were already being forwarded.

Overall

The fix is correct, minimal, and well-tested. The PR accurately diagnoses why #3715 was insufficient (wrong packet type buffered) and why the symptom was intermittent (stale TXT_MSG buffer occasionally matching). Ready to merge.


…eader comment

Addresses the two non-blocking notes from the Claude review of #3737:
- Add the missing matrix case: a direct (pathLen 0xff) GRP_TXT channel message
  buffers an empty hop list and still carries SNR.
- Refresh the file header comment to mention GRP_TXT/ChannelMsgRecv alongside
  TXT_MSG/ContactMsgRecv (was stale after the buffering change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
@Yeraze

Yeraze commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

Thanks — addressed both non-blocking notes in 0bfe7fe:

  • Added the missing matrix case: a direct (pathLen=0xff) GRP_TXT channel message buffers an empty hop list and still carries SNR ({ROUTE}(direct) downstream).
  • Refreshed the stale file header comment to mention GRP_TXT/ChannelMsgRecv alongside TXT_MSG/ContactMsgRecv.

The core implementation points you raised needed no change (correct, GRP_DATA rightly excluded, guards preserved).

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review

  • Read changed files
  • Review new commit (0bfe7fe) addressing previous feedback
  • Post final review

Summary

The previous feedback has been fully addressed. This PR is ready to merge.

New commit (0bfe7fe) review

Direct-path test (line 210–229): The new attaches an empty path for a direct (non-routed) GRP_TXT channel message test correctly covers the matrix gap: pathLen=0xff (direct) on a GRP_TXT packet produces path_hops: [] and still carries SNR. The assertion path_hops: [] is the right expectation — decodePathHops with pathLen=0xff should decode to an empty array, matching DM direct-path behavior.

Updated file header comment (lines 8–10): Now correctly documents both buffering paths:

"still buffer the relay-hash chain for text-message packets so the following recv event can attach it — TXT_MSG (0x02) → ContactMsgRecv (DM) and GRP_TXT (0x05) → ChannelMsgRecv (channel/group), issue #3710."

Accurate and complete.

Test coverage matrix is now complete:

Packet type Path type Test
TXT_MSG routed ✅ existing
TXT_MSG direct ✅ existing (pathLen=0xff)
GRP_TXT routed (1-byte hash) ✅ line 169
GRP_TXT routed (2-byte hash) ✅ line 187 + 384
GRP_TXT direct ✅ line 210 (new)

No issues. The implementation is correct, the tests are comprehensive, and the documentation is accurate.


@Yeraze Yeraze merged commit 5bdc312 into main Jun 25, 2026
20 checks passed
@Yeraze Yeraze deleted the fix/meshcore-route-channel-grptxt-3710 branch June 25, 2026 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant