{"record":{"id":"2a816c83c6e5ae57","repo":"CoplayDev/unity-mcp","slug":"a-unity-test-run-is-already-in-progress","errorCode":null,"errorMessage":"A Unity test run is already in progress.","messagePattern":"A Unity test run is already in progress\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"MCPForUnity/Editor/Services/TestJobManager.cs","lineNumber":337,"sourceCode":"                LastUpdateUnixMs = started,\n                TotalTests = null,\n                CompletedTests = 0,\n                CurrentTestFullName = null,\n                CurrentTestStartedUnixMs = null,\n                LastFinishedTestFullName = null,\n                LastFinishedUnixMs = null,\n                FailuresSoFar = new List<TestJobFailure>(),\n                Error = null,\n                Result = null,\n                InitTimeoutMs = initTimeoutMs\n            };\n\n            // Single lock scope for check-and-set to avoid TOCTOU race\n            lock (LockObj)\n            {\n                if (!string.IsNullOrEmpty(_currentJobId))\n                {\n                    throw new InvalidOperationException(\"A Unity test run is already in progress.\");\n                }\n                Jobs[jobId] = job;\n                _currentJobId = jobId;\n            }\n            PersistToSessionState(force: true);\n\n            // Kick the run (must be called on main thread; our command handlers already run there).\n            Task<TestRunResult> task = MCPServiceLocator.Tests.RunTestsAsync(mode, filterOptions);\n\n            void FinalizeJob(Action finalize)\n            {\n                // Ensure state mutation happens on main thread to avoid Unity API surprises.\n                EditorApplication.delayCall += () =>\n                {\n                    try { finalize(); }\n                    catch (Exception ex) { McpLog.Error($\"[TestJobManager] Finalize failed: {ex.Message}\\n{ex.StackTrace}\"); }\n                };\n            }","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/MCPForUnity/Editor/Services/TestJobManager.cs#L319-L355","documentation":"Thrown by TestJobManager.StartJob under a lock when _currentJobId is already set. The job manager enforces a single concurrent test run (single-slot) with a check-and-set inside one lock scope to avoid a TOCTOU race. This is the outer guard; TestRunnerService has its own inner guard (error 58).","triggerScenarios":"Two run_tests invocations issued before the first finalized (multi-agent concurrency); a previous run whose FinalizeCurrentJobFromRunFinished/continuation never cleared _currentJobId; a PlayMode run still in progress.","commonSituations":"Two AI agents calling run_tests near-simultaneously; a previous job left as a zombie after a crash; a long PlayMode run still active when a second request arrives.","solutions":["Poll the existing job with get_test_status until it finishes before starting another run.","If the job is a zombie (status stuck, no real run), the finalization safety net should clear it; if not, restart the Unity Editor to reset session state.","Serialize run_tests calls from multiple agents with a queue/lock on the caller side."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Check for an active job before starting a new one.\nif (!string.IsNullOrEmpty(TestJobManager.CurrentJobId)) // or expose an IsBusy flag\n    return ErrorResponse(\"A test run is in progress; poll get_test_status before starting another.\");","typeGuard":null,"tryCatchPattern":"try { return TestJobManager.StartJob(mode, opts); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"already in progress\"))\n{ /* poll existing job; queue or reject the new request */ }","preventionTips":["Poll get_test_status until the current job finishes before issuing run_tests again.","Serialize run_tests across multiple agents with a caller-side queue.","If a job is stuck (zombie), restart the Editor to reset session state."],"tags":["test-runner","concurrency","state","lock"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}