{"record":{"id":"34e0c67f17afca79","repo":"can1357/oh-my-pi","slug":"python-execution-is-unavailable-while-session-disp","errorCode":null,"errorMessage":"Python execution is unavailable while session disposal is in progress","messagePattern":"Python execution is unavailable while session disposal is in progress","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/eval-runner.ts","lineNumber":93,"sourceCode":"\t\t\t\t});\n\t\t\tconst result = await executePythonCommand(code, {\n\t\t\t\tcwd,\n\t\t\t\tsessionId: namespacePythonSessionId(sessionId),\n\t\t\t\tkernelOwnerId: this.#kernelOwnerId,\n\t\t\t\tkernelMode: this.#host.settings.get(\"python.kernelMode\"),\n\t\t\t\tinterpreter: this.#host.settings.get(\"python.interpreter\")?.trim() || undefined,\n\t\t\t\tonChunk,\n\t\t\t\tsignal: abortController.signal,\n\t\t\t});\n\t\t\tthis.recordPythonResult(code, result, options);\n\t\t\treturn result;\n\t\t})();\n\t\treturn await this.trackExecution(execution, abortController);\n\t}\n\n\t/** Rejects new eval work once session disposal begins. */\n\tassertExecutionAllowed(): void {\n\t\tif (this.#disposing) throw new Error(\"Python execution is unavailable while session disposal is in progress\");\n\t}\n\n\t/** Tracks externally started Python work so disposal can await and abort it. */\n\ttrackExecution<T>(execution: Promise<T>, abortController: AbortController): Promise<T> {\n\t\tthis.#abortControllers.add(abortController);\n\t\tthis.#activeExecutions.add(execution);\n\t\tvoid execution.then(\n\t\t\t() => {\n\t\t\t\tthis.#abortControllers.delete(abortController);\n\t\t\t\tthis.#activeExecutions.delete(execution);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tthis.#abortControllers.delete(abortController);\n\t\t\t\tthis.#activeExecutions.delete(execution);\n\t\t\t},\n\t\t);\n\t\treturn execution;\n\t}","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/eval-runner.ts#L75-L111","documentation":"EvalRunner.assertExecutionAllowed() guards against starting new Python/eval work once the session's disposal sequence has begun. Once #disposing is set, executePython() and execution() throw this synchronously instead of queueing work that could never complete or would race the teardown.","triggerScenarios":"Calling evalRunner.executePython(...) or evalRunner.execution(...) after dispose() has started on the session — e.g. a background task, timer, or streaming response that fires code execution while the app is shutting the session down.","commonSituations":"App shutdown or session switch while an agent loop is still mid-turn; a queued follow-up tool call executing after the user aborted/disposed; tests tearing down a session while async callbacks still fire.","solutions":["Check a disposed flag / isDisposed before invoking executePython and skip the work","Await dispose() completion before letting pending tasks run, or cancel pending tasks at shutdown start","Wrap late calls in try-catch and treat this error as an expected shutdown signal, not a bug","Reorder teardown so eval work is drained/aborted before disposal begins"],"exampleFix":"// before\nawait evalRunner.executePython(code); // throws during shutdown\n// after\nif (session.isDisposed()) return; // or AbortSignal check\ntry {\n  await evalRunner.executePython(code);\n} catch (e) {\n  if (!(e instanceof Error && e.message.includes('disposal is in progress'))) throw e;\n}","handlingStrategy":"try-catch","validationCode":"// gate calls on session liveness:\n// if (session.isDisposed()) return; // skip eval work on a dying session","typeGuard":null,"tryCatchPattern":"try {\n  const result = await evalRunner.executePython(code);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('disposal is in progress')) {\n    return null; // expected during shutdown — abort quietly\n  }\n  throw err;\n}","preventionTips":["Pass an AbortSignal linked to session disposal into long-running agent loops","Cancel or await in-flight eval work before calling dispose()","Never schedule new tool/eval calls from timers that outlive the session","In tests, await session teardown before ending the test to avoid stray callbacks"],"tags":["lifecycle","disposal","python","race-condition"],"backgroundTag":"operation-after-dispose","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}