Unity-Technologies/ml-agents · critical · UnityAgentsException
{m_MethodName} called recursively. This might happen if you
Error message
{m_MethodName} called recursively. This might happen if you call EnvironmentStep() or EndEpisode() from custom code such as CollectObservations() or OnActionReceived(). What it means
RecursionChecker.Start() throws when the wrapped stepping method is entered while it is already running. It guards against re-entrant simulation steps, which corrupt the episode state. Typically caused by user code calling EnvironmentStep() or EndEpisode() from inside callbacks like CollectObservations() or OnActionReceived().
Source
Thrown at com.unity.ml-agents/Runtime/RecursionChecker.cs:19
using System;
namespace Unity.MLAgents
{
internal class RecursionChecker : IDisposable
{
private bool m_IsRunning;
private string m_MethodName;
public RecursionChecker(string methodName)
{
m_MethodName = methodName;
}
public IDisposable Start()
{
if (m_IsRunning)
{
throw new UnityAgentsException(
$"{m_MethodName} called recursively. " +
"This might happen if you call EnvironmentStep() or EndEpisode() from custom " +
"code such as CollectObservations() or OnActionReceived()."
);
}
m_IsRunning = true;
return this;
}
public void Dispose()
{
// Reset the flag when we're done (or if an exception occurred).
m_IsRunning = false;
}
}
}
View on GitHub (pinned to 3ecb446f75)
Solutions
- Remove EnvironmentStep()/EndEpisode() calls from CollectObservations(), OnActionReceived(), and sensor code
- Set a flag in the callback and act on it in Update() or the next natural step instead of stepping inline
- Use Agent.EndEpisode() only from event handlers outside the step pipeline (e.g. FixedUpdate after a trigger check, not during observation collection)
- If a reset is needed mid-step, defer it via Agent_Initialize/EpisodeInterrupted or request it through the Agent's built-in reset path
Example fix
// before
public override void CollectObservations(VectorSensor sensor)
{
if (badState) EndEpisode(); // recursive
}
// after
private bool needsReset;
public override void CollectObservations(VectorSensor sensor)
{
if (badState) needsReset = true;
}
void FixedUpdate() { if (needsReset) { needsReset = false; EndEpisode(); } } Defensive patterns
Strategy: try-catch
Validate before calling
// Guard user callbacks: never call EnvironmentStep/EndEpisode inside them
bool inStep = false;
void SafeEndEpisode(Agent agent)
{
if (!inStep) agent.EndEpisode();
else agent.EndEpisodeRequested = true; // handle after step completes
} Try / catch
try
{
Academy.Instance.EnvironmentStep();
}
catch (UnityAgentsException e) when (e.Message.Contains("called recursively"))
{
Debug.LogError("EnvironmentStep/EndEpisode was called from inside the step pipeline: " + e.Message);
} Prevention
- Never call EnvironmentStep() or EndEpisode() from CollectObservations(), OnActionReceived(), or sensor code
- Defer resets to FixedUpdate/Update via a pending-reset flag
- Keep stepping owned by a single driver (Academy or your training loop), not multiple code paths
When it happens
Trigger: Calling Academy.EnvironmentStep() inside CollectObservations(), OnActionReceived(), or another sensor callback; calling Agent.EndEpisode() from within CollectObservations(); custom editor/debug code invoking EnvironmentStep() from an event fired during the step.
Common situations: Beginners trying to force the environment forward when they need a new observation; resetting an episode mid-observation-collection after detecting a bad state; event-driven designs that step the environment from UI callbacks triggered by simulation events.
Related errors
- Index out of bounds, expected a number between 0 and {Length
- Enumerator not started.
- Enumerator has reached the end already.
- Action spaces with both continuous and discrete actions are
- Call to SendInfoToBrain when Agent hasn't been initialized.P
AI-assisted analysis of Unity-Technologies/ml-agents@3ecb446f75 (2026-09-02).
Data as JSON: /api/errors/91d15ac2ffa1c5e7.
Report an issue: GitHub.