{"record":{"id":"672e13623fe5bd5a","repo":"can1357/oh-my-pi","slug":"failed-to-dispose-one-or-more-eval-kernels","errorCode":null,"errorMessage":"Failed to dispose one or more eval kernels","messagePattern":"Failed to dispose one or more eval kernels","errorType":"exception","errorClass":"AggregateError","httpStatus":null,"severity":"warning","filePath":"packages/coding-agent/src/session/eval-runner.ts","lineNumber":189,"sourceCode":"\tbeginDispose(): void {\n\t\tthis.#disposing = true;\n\t}\n\n\t/** Waits for active work and disposes every retained eval kernel owned by the session. */\n\tasync disposeKernels(): Promise<void> {\n\t\tconst settled = await this.#prepareExecutionsForDispose();\n\t\tif (!settled) {\n\t\t\tlogger.warn(\"Detaching retained eval-kernel ownership during dispose while eval execution is still active\");\n\t\t}\n\t\tconst results = await Promise.allSettled([\n\t\t\tdisposeKernelSessionsByOwner(this.#kernelOwnerId),\n\t\t\tdisposeRubyKernelSessionsByOwner(this.#kernelOwnerId),\n\t\t\tdisposeJuliaKernelSessionsByOwner(this.#kernelOwnerId),\n\t\t\tdisposeVmContextsByOwner(this.#kernelOwnerId),\n\t\t]);\n\t\tconst errors: unknown[] = [];\n\t\tfor (const result of results) if (result.status === \"rejected\") errors.push(result.reason);\n\t\tif (errors.length > 0) throw new AggregateError(errors, \"Failed to dispose one or more eval kernels\");\n\t}\n\n\tasync #waitForExecutionsToSettle(timeoutMs: number): Promise<boolean> {\n\t\tconst deadline = Date.now() + timeoutMs;\n\t\twhile (this.#activeExecutions.size > 0) {\n\t\t\tconst remainingMs = deadline - Date.now();\n\t\t\tif (remainingMs <= 0) return false;\n\t\t\tconst settled = await Promise.race([\n\t\t\t\tPromise.allSettled(Array.from(this.#activeExecutions)).then(() => true),\n\t\t\t\tBun.sleep(remainingMs).then(() => false),\n\t\t\t]);\n\t\t\tif (!settled && this.#activeExecutions.size > 0) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tasync #prepareExecutionsForDispose(): Promise<boolean> {\n\t\tif (!(await this.#waitForExecutionsToSettle(3_000))) {","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/eval-runner.ts#L171-L207","documentation":"During EvalRunner disposal, disposeKernels() tears down Python, Ruby, Julia kernel sessions and VM contexts in parallel via Promise.allSettled. If any teardown promise rejects, all failure reasons are collected into an AggregateError with this message, so one bad kernel does not hide the others' failures.","triggerScenarios":"Calling dispose()/disposeKernels() when at least one of disposePythonKernelSessionsByOwner, disposeRubyKernelSessionsByOwner, disposeJuliaKernelSessionsByOwner, or disposeVmContextsByOwner rejects — e.g. the kernel process already died and its cleanup IPC fails.","commonSituations":"Kernel process crashed earlier (OOM, killed by OS) leaving stale registration that cleanup cannot remove; shutdown timeout racing kernel shutdown; platform quirk in one language's kernel teardown; repeated disposal attempts.","solutions":["Iterate error.errors on the AggregateError to see which kernel(s) failed and why","Retry dispose() once — stale kernels are often already gone and a second pass succeeds","Kill leftover kernel processes by the owner id manually if cleanup IPC is stuck","Log and continue: this happens during teardown, so partial failure usually means resources were already being reclaimed"],"exampleFix":"// before\ntry { await evalRunner.dispose(); } catch (e) { throw e; }\n// after\ntry { await evalRunner.dispose(); } catch (e) {\n  if (e instanceof AggregateError) {\n    for (const inner of e.errors) logger.warn('kernel dispose failed', { inner: String(inner) });\n  } // teardown errors are usually non-fatal\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isKernelDisposeFailure(err: unknown): err is AggregateError {\n  return err instanceof AggregateError && err.message === 'Failed to dispose one or more eval kernels';\n}","tryCatchPattern":"try {\n  await evalRunner.dispose();\n} catch (err) {\n  if (err instanceof AggregateError && err.message.includes('eval kernels')) {\n    for (const inner of err.errors) logger.warn('kernel dispose failed', { inner: String(inner) });\n    return; // teardown best-effort; do not mask shutdown\n  }\n  throw err;\n}","preventionTips":["Treat disposal errors as best-effort — log each error.errors entry and continue shutdown","Retry dispose once if kernels crashed previously","Monitor kernel health so crashed kernels are reaped before teardown","Run disposal with a timeout so a stuck kernel cannot hang shutdown"],"tags":["disposal","kernel","aggregate-error","python"],"backgroundTag":"kernel-dispose-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}