Skip to content

fix(channel-detection): prevent phantom swaps when channels share PSK+name (#3452)#3453

Merged
Yeraze merged 1 commit into
mainfrom
claude/great-dijkstra-kgr8qw
Jun 14, 2026
Merged

fix(channel-detection): prevent phantom swaps when channels share PSK+name (#3452)#3453
Yeraze merged 1 commit into
mainfrom
claude/great-dijkstra-kgr8qw

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #3452detectChannelMoves in src/server/utils/channelMoveDetection.ts produced phantom bidirectional swap detections whenever two channels shared the same (psk, name) pair. On every config sync this triggered migrateMessagesForChannelMoves, progressively scrambling message↔channel attribution.

Root cause: the matcher had no uniqueness guard — a duplicate identity in either snapshot caused each side to match the other, generating a bogus slot X→Y, slot Y→X swap that recurred forever.

Fix: before matching, count occurrences of each (psk, name) key in both the before and after snapshots. If the key appears more than once in either snapshot the identity is ambiguous — skip it. Messages stay in place (no corruption), which is strictly safer than mis-migrating them. Unique identities are still matched correctly, so genuine single-moves and genuine swaps continue to work.

Changes

  • src/server/utils/channelMoveDetection.ts — added channelKey / countByKey helpers; detectChannelMoves now guards on uniqueness before matching
  • src/server/utils/channelMoveDetection.test.ts — 4 new regression tests covering the phantom-swap scenario, the mixed unique+duplicate case, and the unchanged genuine-swap path

Test plan

  • npm test — all 10 tests in channelMoveDetection.test.ts pass (6 original + 4 new regression tests)
  • CI passes
  • Manually verify: two channels with identical PSK+name no longer produce repeated channel_migration_on_startup entries in audit_log

https://claude.ai/code/session_01P8be9VE84SBL1LoGefsQ3c


Generated by Claude Code

…antom swaps (#3452)

Duplicate channel identities (same PSK + name) caused detectChannelMoves to
emit phantom bidirectional swaps on every config sync, progressively corrupting
message-to-channel attribution. Guard now requires the (psk, name) key to be
unique in both before and after snapshots before matching; ambiguous identities
are skipped (messages stay put) rather than incorrectly migrated.

https://claude.ai/code/session_01P8be9VE84SBL1LoGefsQ3c
@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


Code Review Complete ✅

Reviewing PR #3453 for channel detection phantom swap fix.

Todo List

  • Read and analyze the modified channel detection logic
  • Review the test coverage and new regression tests
  • Check for potential bugs, edge cases, and performance issues
  • Evaluate security considerations
  • Provide comprehensive feedback on code quality

Overall Assessment: Excellent Fix 🎯

This PR provides a clean, well-tested solution to a tricky edge case. The phantom swap issue was correctly identified and the fix is both safe and effective.

✅ Code Quality & Best Practices

Strong points:

  • Clear, focused solution: The uniqueness guard elegantly solves the core problem without over-engineering
  • Excellent documentation: JSDoc comments clearly explain the purpose and the specific issue being addressed
  • Good separation of concerns: Helper functions (channelKey, countByKey) improve readability and testability
  • Fail-safe approach: When identity is ambiguous, the function skips matching rather than guessing - this is the right defensive strategy

Minor suggestions:

  • The channelKey function could benefit from a comment explaining the JSON.stringify approach for composite keys
  • Consider adding a JSDoc comment to countByKey explaining it only counts channels with valid PSKs

✅ Logic & Implementation

The fix is sound:

  • Root cause correctly identified: Duplicate (psk, name) pairs causing bidirectional matches
  • Appropriate guard condition: (beforeCounts.get(k) ?? 0) !== 1 || (afterCounts.get(k) ?? 0) !== 1 ensures uniqueness in both snapshots
  • Preserves existing functionality: Genuine single moves and unique swaps continue to work correctly
  • Safe fallback: Ambiguous identities are skipped, preventing data corruption

Edge cases handled:

  • Empty/null PSK channels are properly skipped (src/server/utils/channelMoveDetection.ts:39)
  • Duplicate move detection prevents redundant entries (src/server/utils/channelMoveDetection.ts:49-51)
  • Mixed scenarios (unique + duplicate channels) work correctly

✅ Test Coverage - Comprehensive

Excellent regression test suite:

The test names clearly describe scenarios, making future maintenance easier.

✅ Performance Considerations

Efficient implementation:

  • O(n) preprocessing: countByKey makes a single pass to build the frequency map
  • O(n) main loop: Single iteration through the before snapshot
  • Early termination: Continues to next channel when duplicates detected
  • Minimal memory overhead: Uses simple Map for counting

For typical channel configurations (2-8 channels), performance impact is negligible.

✅ Security Considerations

No security concerns identified:

  • Input validation: Function properly handles null/undefined PSK values
  • No injection risks: Uses safe JSON.stringify for key generation
  • Defensive programming: Guards against edge cases that could cause incorrect data migration
  • Data integrity: Prevents corruption of message↔channel attribution

✅ Architecture Alignment

Fits well with existing codebase:

  • Consistent patterns: Follows established TypeScript/testing conventions
  • Interface compatibility: No breaking changes to the public API
  • Error handling: Graceful degradation when faced with ambiguous data

Summary

This is a high-quality fix that:

  • ✅ Correctly addresses the root cause of phantom swaps
  • ✅ Includes comprehensive test coverage
  • ✅ Uses safe, defensive programming practices
  • ✅ Maintains backward compatibility
  • ✅ Has minimal performance impact

Recommendation: Approve and merge 🚀

The solution is production-ready and effectively resolves issue #3452 without introducing new risks.

@Yeraze Yeraze merged commit 2960455 into main Jun 14, 2026
19 checks passed
@Yeraze Yeraze mentioned this pull request Jun 14, 2026
Yeraze added a commit that referenced this pull request Jun 14, 2026
Bump version to 4.10.3 across package.json, package-lock.json,
helm/meshmonitor/Chart.yaml, desktop/package.json, and
desktop/src-tauri/tauri.conf.json.

Finalize the CHANGELOG: promote [Unreleased] to [4.10.3] and add the
entries merged after the section was last written — MQTT NodeInfo
clobber fix (#3456), Traffic Management/StatusMessage firmware-version
gate (#3457), local-node module config storage (#3460), phantom channel
swap fix (#3453), and the TX power help-text clarification (#3459).

Add a release blog post highlighting the Traffic Management detection
fix and the bug-fix round.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

[BUG] detectChannelMoves reports phantom bidirectional swaps when two channels share the same PSK+name, repeatedly mis-migrating messages

2 participants