fix(coding-agent): addressed python cleanup review findings

fixed retained-kernel restart and owner cleanup edge cases during recovery and disposal

tracked async user_python hooks during disposal-sensitive execution paths and hardened startup warmup tracking

strengthened cleanup and kernel lifecycle regressions to remove deadlocks, false positives, and timing flakes
This commit is contained in:
Carl
2026-04-12 14:45:19 +02:00
parent 379064a0c9
commit 2b480f7da1
11 changed files with 278 additions and 122 deletions
@@ -100,25 +100,23 @@ describe("executePython lifecycle", () => {
expect(kernelNext.execute).toHaveBeenCalledTimes(1);
});
it("retries a dead session restart after an unconfirmed shutdown", async () => {
it("restarts dead retained sessions even when shutdown confirmation is missing", async () => {
const kernel = new FakeKernel(OK_RESULT);
const kernelNext = new FakeKernel(OK_RESULT);
kernel.alive = false;
kernel.shutdown.mockResolvedValueOnce({ confirmed: false }).mockResolvedValueOnce({ confirmed: true });
kernel.shutdown.mockResolvedValueOnce({ confirmed: false });
vi.spyOn(pythonKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
const startSpy = vi
.spyOn(pythonKernel.PythonKernel, "start")
.mockResolvedValueOnce(kernel as unknown as pythonKernel.PythonKernel)
.mockResolvedValueOnce(kernelNext as unknown as pythonKernel.PythonKernel);
await expect(
executePython("1 + 1", { kernelMode: "session", sessionId: "retry-dead-session", cwd: getProjectDir() }),
).rejects.toThrow("Failed to confirm crashed kernel shutdown before restart");
await executePython("1 + 1", { kernelMode: "session", sessionId: "retry-dead-session", cwd: getProjectDir() });
await executePython("2 + 2", { kernelMode: "session", sessionId: "retry-dead-session", cwd: getProjectDir() });
expect(startSpy).toHaveBeenCalledTimes(2);
expect(kernel.shutdown).toHaveBeenCalledTimes(2);
expect(kernel.shutdown).toHaveBeenCalledTimes(1);
expect(kernel.execute).toHaveBeenCalledTimes(0);
expect(kernelNext.execute).toHaveBeenCalledTimes(1);
expect(kernelNext.execute).toHaveBeenCalledTimes(2);
});
});