diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
index c062e7ee..b2ca2708 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs
@@ -421,6 +421,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
ResubscribeDesired();
AttachAlarmSource();
SubscribeDesiredAlarms();
+ StartDiscovery(); // re-run discovery on reconnect — keeps the injected tree fresh if the backend's capabilities changed
});
// A failure here is a no-op regardless of generation — the retry timer keeps trying the
// current config; only a (generation-matched) InitializeSucceeded transitions state.
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDiscoveryTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDiscoveryTests.cs
index a97542c7..f880fc3e 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDiscoveryTests.cs
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDiscoveryTests.cs
@@ -77,6 +77,47 @@ public sealed class DriverInstanceActorDiscoveryTests : RuntimeActorTestBase
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
}
+ ///
+ /// Discovery RE-RUNS on every return to Connected: after the initial discovery settles, a
+ /// drives the actor through Reconnecting and
+ /// back to Connected (via the auto-retry timer, the same path the existing reconnect tests use),
+ /// and a fresh bounded discovery loop fires — keeping the injected tree current if the backend's
+ /// capabilities changed across the reconnect. The new init bumps the generation, so any
+ /// pre-reconnect tick is discarded by the generation guard (the initial loop has already settled
+ /// here, so none are in flight).
+ ///
+ [Fact]
+ public void Discovery_reruns_after_reconnect()
+ {
+ var driver = new DiscoverableStubDriver();
+ var parent = CreateTestProbe();
+ // Tiny reconnect + rediscover intervals so the whole reconnect-then-rediscover cycle runs fast.
+ var actor = parent.ChildActorOf(DriverInstanceActor.Props(
+ driver,
+ reconnectInterval: TimeSpan.FromMilliseconds(50),
+ rediscoverInterval: TimeSpan.FromMilliseconds(20)));
+
+ actor.Tell(new DriverInstanceActor.InitializeRequested("{}"));
+
+ // Drain the initial settling passes (0,0,3,3) and confirm the first loop stopped.
+ for (var i = 0; i < 4; i++)
+ parent.ExpectMsg(TimeSpan.FromSeconds(2));
+ parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
+ var passesBeforeReconnect = driver.DiscoverCount; // 4
+
+ // Force a reconnect: Connected → Reconnecting → (auto retry-connect) → Connected again.
+ actor.Tell(new DriverInstanceActor.ForceReconnect());
+
+ // A fresh discovery pass must arrive after the reconnect — the cache is warm now, so it sees
+ // the stable 3-node set immediately.
+ var afterReconnect = parent.ExpectMsg(TimeSpan.FromSeconds(3));
+ afterReconnect.Nodes.Count.ShouldBe(3);
+ afterReconnect.DriverInstanceId.ShouldBe(driver.DriverInstanceId);
+
+ // The driver was discovered again — proves a fresh loop ran, not a replay of the old one.
+ driver.DiscoverCount.ShouldBeGreaterThan(passesBeforeReconnect);
+ }
+
///
/// A that also exposes . Each DiscoverAsync
/// pass is counted; passes 1–2 yield nothing (cache warming), passes 3+ yield a stable 3-node set —