louthy/language-ext · error · InvalidOperationException
Invalid iterator
Error message
Invalid iterator
What it means
IO.RepeatUntil builds its iteration via an internal enumerable/iterator of IO steps; a switch in the implementation expects a specific head shape (a Cons-like iterator node). If the head is anything else — an empty/terminated iterator or an unexpected IO node — the implementation cannot construct the loop and throws InvalidOperationException("Invalid iterator"). Repeat and RepeatWhile funnel through this code path.
Solutions
- Ensure the repeated effect is a non-empty, standard IO (e.g. built with IO.pure/IO.lift or plain effects), not an empty/custom iterator node.
- Construct the loop with the intended public API: `effect.RepeatUntil(predicate)` on a real effect rather than assembling DSL nodes manually.
- If a custom IO node is involved, make it derive from a recognized IO form, or perform your own recursive loop with Bind and a termination check.
- Check your LanguageExt version; if you rely on internal iterator shapes, align with the representation in your installed version.
Example fix
// before — feeds an empty/custom iterator node into the repeater
IO<int> loop = customIteratorNode.RepeatUntil(v => done(v)); // throws
// after — recursive Bind loop with explicit termination
IO<int> loop = default(IO<int>);
loop = IO<int>.lift(() => runStep())
.Bind(v => predicate(v) ? IO.pure(v) : go(tail, v));
// or simply: bodyEffect.RepeatUntil(v => predicate(v)) on a standard IO Defensive patterns
Strategy: validation
Validate before calling
static IO<T> requireNonEmpty<T>(IO<T> io, string ctx) =>
io is null || io is IOTail<T>
? throw new ArgumentException($"{ctx}: effect must be a standard non-empty IO")
: io; Type guard
bool isRepeatable<T>(IO<T> io) =>
io is not null && io is not IOTail<T> && io is not IO<T>.Empty; Try / catch
try { loop = effect.RepeatUntil(p); }
catch (InvalidOperationException ex) when (ex.Message == "Invalid iterator")
{
loop = runManualLoop(effect, p); // fallback recursive Bind loop
} Prevention
- Only feed standard, non-empty IO effects into Repeat/RepeatUntil/RepeatWhile.
- Don't hand-construct DSL iterator nodes for loop bodies; use IO.pure/lift or public combinators.
- Pin/verify the LanguageExt version if you depend on internal IO representations.
- For unusual loops, write your own recursive Bind with an explicit termination predicate.
When it happens
Trigger: Calling IO.RepeatUntil / Repeat / RepeatWhile with an effect whose internal iteration source yields no steps or a non-standard IO node — typically when the head IO passed to Repeat is not one of the recognized iterator forms the implementation switches on.
Common situations: Passing an empty or immediately-completed IO as the looping body; building the repeated effect through custom DSL nodes that the internal switch doesn't recognize; library version changes where iterator representation changed and old constructed nodes no longer match.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- You can't map a tail call
- You can't chain a tail call
- Tail calls can't transform in the `select`
- Nil iterator has no head
- Nil iterator has no head
AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15).
Data as JSON: /api/errors/ccc854b12c036633.
Report an issue: GitHub.
Appendix: source
Thrown at LanguageExt.Core/Effects/IO/IO.cs:545
Schedule schedule,
Func<A, bool> predicate)
{
return go(schedule.PrependZero.Run().GetIterator(), default);
IO<A> go(Iterator<Duration> iter, A? value) =>
iter switch
{
Iterator<Duration>.Nil =>
IO.pure<A>(value!),
Iterator<Duration>.Cons(var head, var tail) =>
IO.yieldFor(head)
.Bind(_ => Bracket()
.Bind(v => predicate(v)
? IO.pure(v)
: go(tail, v))),
_ => throw new InvalidOperationException("Invalid iterator")
};
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Retrying the effect when it fails
//
/// <summary>
/// Retry if the IO computation fails
/// </summary>
/// <remarks>
/// This variant will retry forever
/// </remarks>
/// <remarks>
/// Any resources acquired within a retrying IO computation will automatically be released *if* the operation fails.
/// So, successive retries will not grow the acquired resources on each retry iteration. Any successful operation that
/// acquires resources will have them tracked in the usual way. View on GitHub (pinned to 2f0e362824)