diff --git a/src/MeshWeaver.Fixture/HubFactAttribute.cs b/src/MeshWeaver.Fixture/HubFactAttribute.cs
index ed5f5ef416..21b0131424 100644
--- a/src/MeshWeaver.Fixture/HubFactAttribute.cs
+++ b/src/MeshWeaver.Fixture/HubFactAttribute.cs
@@ -10,17 +10,30 @@ namespace MeshWeaver.Fixture;
public class HubFactAttribute : FactAttribute;
#else
///
-/// Marks a hub test method. In non-DEBUG builds a 5 second timeout is applied
-/// so that a wedged hub fails the test fast instead of hanging the run.
+/// Marks a hub test method. In non-DEBUG builds a timeout is applied so that a wedged hub fails
+/// the test instead of hanging the run.
+///
+/// 🚨 The budget must exceed what the MESH FIXTURE costs on a loaded CI runner, or the test
+/// measures the runner instead of the code. It was 5s, and 5s is not achievable there: a CI log for
+/// MeshNodeVersionSyncTest (2026-07-27) shows class init at 19:59:22.595 and dispose at
+/// 19:59:30.720 — 8.1s of fixture before the body runs at all. That produced a recurring
+/// main-red flake with no defect behind it, and the same trap bit a heartbeat test written the same
+/// night ("Test execution timed out after 5000 milliseconds", passing locally where no budget is
+/// enforced).
+///
+/// 30s matches the runner-wide methodTimeout and still fails a genuine wedge well
+/// before the run hangs — a wedged read surfaces its own GetMeshNode … timed out after 60.0s
+/// first, so nothing is masked by the larger budget.
///
public sealed class HubFactAttribute : FactAttribute
{
///
- /// Initializes a new instance and sets the test timeout to 5000 milliseconds.
+ /// Initializes a new instance and sets the test timeout to 30000 milliseconds — above the
+ /// measured CI fixture cost, in line with the runner-wide methodTimeout.
///
public HubFactAttribute()
{
- Timeout = 5000;
+ Timeout = 30000;
}
};
#endif
diff --git a/test/MeshWeaver.Persistence.Test/MeshNodeVersionSyncTest.cs b/test/MeshWeaver.Persistence.Test/MeshNodeVersionSyncTest.cs
index 24771b285f..ac3b9f2d65 100644
--- a/test/MeshWeaver.Persistence.Test/MeshNodeVersionSyncTest.cs
+++ b/test/MeshWeaver.Persistence.Test/MeshNodeVersionSyncTest.cs
@@ -190,7 +190,7 @@ public override async ValueTask DisposeAsync()
}
}
- [Fact(Timeout = 10000)]
+ [Fact(Timeout = 30000)]
public async Task MeshNode_InitialVersion_IsZero()
{
// Arrange - check initial version via the authoritative owner-hub read
@@ -201,7 +201,7 @@ public async Task MeshNode_InitialVersion_IsZero()
initialNode!.Version.Should().Be(0, "initial version should be 0");
}
- [Fact(Timeout = 10000)]
+ [Fact(Timeout = 30000)]
public async Task MeshNode_VersionProperty_CanBeSetAndPersisted()
{
// Arrange - create a node with a specific version. Use a built-in
@@ -227,7 +227,7 @@ public async Task MeshNode_VersionProperty_CanBeSetAndPersisted()
savedNode!.Version.Should().Be(42, "version should be preserved in persistence");
}
- [Fact(Timeout = 10000)]
+ [Fact(Timeout = 30000)]
public void MessageHub_HasSetInitialVersionMethod()
{
// Verify that IMessageHub interface has the SetInitialVersion method