diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f23dd81..74a41e2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,7 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
   - This makes it obvious when hardware acceleration will not work as configured
 - Add button in schedule editor to clone schedule item
 - Allow YAML playout sequence definitions to reference other sequences
-  - Playout builder will behave in unexpected ways if nesting is too deep
+  - Cycles will be detected and logged, and sequences with cycles will prevent the playout from building
 - Add `repeat` property to YAML sequence instruction
   - This tells the playout builder how many times this sequence should repeat
   - Omitting this value is the same as setting it to `1`
diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs
index d07329b9..23f9e1ec 100644
--- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs
+++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs
@@ -6,6 +6,7 @@ using ErsatzTV.Core.Interfaces.Repositories;
 using ErsatzTV.Core.Interfaces.Scheduling;
 using ErsatzTV.Core.Scheduling.YamlScheduling.Handlers;
 using ErsatzTV.Core.Scheduling.YamlScheduling.Models;
+using ErsatzTV.Core.Search;
 using Microsoft.Extensions.Logging;
 using YamlDotNet.Serialization;
 using YamlDotNet.Serialization.NamingConventions;
@@ -127,10 +128,16 @@ public class YamlPlayoutBuilder(
             }
         }
 
-        int flattenCount = 0;
+        if (DetectCycle(context.Definition))
+        {
+            logger.LogError("YAML sequence contains a cycle; unable to build playout");
+            return playout;
+        }
+
+        var flattenCount = 0;
         while (context.Definition.Playout.Any(x => x is YamlPlayoutSequenceInstruction))
         {
-            if (flattenCount > 10)
+            if (flattenCount > 100)
             {
                 logger.LogError(
                     "YAML playout definition contains sequence nesting that is too deep; this introduces undefined behavior");
@@ -197,6 +204,21 @@ public class YamlPlayoutBuilder(
         return playout;
     }
 
+    private static bool DetectCycle(YamlPlayoutDefinition definition)
+    {
+        var graph = new AdjGraph();
+
+        foreach (YamlPlayoutSequenceItem sequence in definition.Sequence)
+        {
+            foreach (YamlPlayoutSequenceInstruction instruction in sequence.Items.OfType<YamlPlayoutSequenceInstruction>())
+            {
+                graph.AddEdge(sequence.Key, instruction.Sequence);
+            }
+        }
+
+        return graph.HasAnyCycle();
+    }
+
     private async Task<int> GetDaysToBuild() =>
         await configElementRepository
             .GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
diff --git a/ErsatzTV.Infrastructure/Search/AdjGraph.cs b/ErsatzTV.Core/Search/AdjGraph.cs
similarity index 89%
rename from ErsatzTV.Infrastructure/Search/AdjGraph.cs
rename to ErsatzTV.Core/Search/AdjGraph.cs
index b23d8fed..fa15b2b6 100644
--- a/ErsatzTV.Infrastructure/Search/AdjGraph.cs
+++ b/ErsatzTV.Core/Search/AdjGraph.cs
@@ -1,4 +1,4 @@
-namespace ErsatzTV.Infrastructure.Search;
+namespace ErsatzTV.Core.Search;
 
 public class AdjGraph
 {
@@ -21,6 +21,11 @@ public class AdjGraph
         return HasCycleImpl(from.ToLowerInvariant(), visited, stack);
     }
 
+    public bool HasAnyCycle()
+    {
+        return _edges.Any(edge => HasCycle(edge.From));
+    }
+
     private bool HasCycleImpl(string node, ISet<string> visited, ISet<string> stack)
     {
         if (stack.Contains(node))