diff --git a/repeater/packet_router.py b/repeater/packet_router.py index 2002390..0b5024e 100644 --- a/repeater/packet_router.py +++ b/repeater/packet_router.py @@ -237,8 +237,13 @@ class PacketRouter: # Route to specific handlers for parsing only if payload_type == TraceHandler.payload_type(): - # Process trace packet - if self.daemon.trace_helper: + # Locally injected TRACE requests are TX-only and re-enter the router so + # companion delivery can still happen. They are not inbound RF responses, + # so skip TraceHelper parsing to avoid matching pending ping tags against + # zeroed local metadata. + if getattr(packet, "_injected_for_tx", False): + processed_by_injection = True + elif self.daemon.trace_helper: await self.daemon.trace_helper.process_trace_packet(packet) # Skip engine processing for trace packets - they're handled by trace helper processed_by_injection = True diff --git a/tests/test_packet_router.py b/tests/test_packet_router.py index 38c1fbb..2b46514 100644 --- a/tests/test_packet_router.py +++ b/tests/test_packet_router.py @@ -19,6 +19,8 @@ import time import unittest from unittest.mock import AsyncMock, MagicMock, patch +from pymc_core.node.handlers.trace import TraceHandler + from repeater.packet_router import PacketRouter @@ -158,6 +160,26 @@ class TestInFlightCap(unittest.IsolatedAsyncioTestCase): self.assertEqual(router._cap_drop_count, 0) await router.stop() + async def test_injected_trace_packet_skips_inbound_trace_processing(self): + """Locally injected TRACE packets must not be re-parsed as inbound trace responses.""" + daemon = _make_daemon() + daemon.trace_helper = MagicMock() + daemon.trace_helper.process_trace_packet = AsyncMock() + + router = PacketRouter(daemon) + pkt = _make_packet(payload_type=TraceHandler.payload_type()) + + await router.start() + try: + injected = await router.inject_packet(pkt) + self.assertTrue(injected) + await asyncio.sleep(0.05) + + daemon.repeater_handler.assert_awaited_once() + daemon.trace_helper.process_trace_packet.assert_not_awaited() + finally: + await router.stop() + # ── 3. Shutdown: in-flight tasks drained ──────────────────────────────── async def test_stop_waits_for_in_flight_tasks(self):