{"record":{"id":"91d15ac2ffa1c5e7","repo":"Unity-Technologies/ml-agents","slug":"m-methodname-called-recursively-this-might-happ","errorCode":null,"errorMessage":"{m_MethodName} called recursively. This might happen if you call EnvironmentStep() or EndEpisode() from custom code such as CollectObservations() or OnActionReceived().","messagePattern":"(.+?) called recursively\\. This might happen if you call EnvironmentStep\\(\\) or EndEpisode\\(\\) from custom code such as CollectObservations\\(\\) or OnActionReceived\\(\\)\\.","errorType":"exception","errorClass":"UnityAgentsException","httpStatus":null,"severity":"critical","filePath":"com.unity.ml-agents/Runtime/RecursionChecker.cs","lineNumber":19,"sourceCode":"using System;\n\nnamespace Unity.MLAgents\n{\n    internal class RecursionChecker : IDisposable\n    {\n        private bool m_IsRunning;\n        private string m_MethodName;\n\n        public RecursionChecker(string methodName)\n        {\n            m_MethodName = methodName;\n        }\n\n        public IDisposable Start()\n        {\n            if (m_IsRunning)\n            {\n                throw new UnityAgentsException(\n                    $\"{m_MethodName} called recursively. \" +\n                    \"This might happen if you call EnvironmentStep() or EndEpisode() from custom \" +\n                    \"code such as CollectObservations() or OnActionReceived().\"\n                );\n            }\n            m_IsRunning = true;\n            return this;\n        }\n\n        public void Dispose()\n        {\n            // Reset the flag when we're done (or if an exception occurred).\n            m_IsRunning = false;\n        }\n    }\n}\n","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/Unity-Technologies/ml-agents/blob/3ecb446f75d1e7400eb404c562dc005d3164cffc/com.unity.ml-agents/Runtime/RecursionChecker.cs#L1-L36","documentation":"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().","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\npublic override void CollectObservations(VectorSensor sensor)\n{\n    if (badState) EndEpisode(); // recursive\n}\n// after\nprivate bool needsReset;\npublic override void CollectObservations(VectorSensor sensor)\n{\n    if (badState) needsReset = true;\n}\nvoid FixedUpdate() { if (needsReset) { needsReset = false; EndEpisode(); } }","handlingStrategy":"try-catch","validationCode":"// Guard user callbacks: never call EnvironmentStep/EndEpisode inside them\nbool inStep = false;\nvoid SafeEndEpisode(Agent agent)\n{\n    if (!inStep) agent.EndEpisode();\n    else agent.EndEpisodeRequested = true; // handle after step completes\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    Academy.Instance.EnvironmentStep();\n}\ncatch (UnityAgentsException e) when (e.Message.Contains(\"called recursively\"))\n{\n    Debug.LogError(\"EnvironmentStep/EndEpisode was called from inside the step pipeline: \" + e.Message);\n}","preventionTips":["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"],"tags":["unity","ml-agents","recursion","environment-step"],"backgroundTag":"recursive-call-detected","analyzedSha":"3ecb446f75d1e7400eb404c562dc005d3164cffc","analyzedAt":"2026-09-02T16:33:12.832Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T21:17:11.164Z"}