can1357/oh-my-pi · error · Error
Cannot ${action} on a disposed JS runtime
Error message
Cannot ${action} on a disposed JS runtime What it means
`#activateGlobals` is the guard run before any operation that needs the runtime's globals activated (setCwd, setRunScope, run). Once the JS runtime is disposed, its global-scope claim has been released, so any further use throws `Cannot <action> on a disposed JS runtime`. It prevents resurrecting global state after teardown and confusing cross-runtime ownership.
Source
Thrown at packages/coding-agent/src/eval/js/shared/runtime.ts:183
* Shared JS runtime for the eval worker and the browser tab worker. Owns the prelude,
* helper bag, console bridge, and indirect-eval execution. Emits text/display/tool-call
* back through `RuntimeHooks` that the embedder supplies — wire format is the embedder's
* concern.
*/
export class JsRuntime {
#globalOwner = Symbol("JsRuntime globals");
#ownedGlobalKeys = new Set<string>();
#disposed = false;
#runHookResolver = () => this.#als.getStore()?.hooks;
#ownGlobal(key: string): void {
if (this.#ownedGlobalKeys.has(key)) return;
claimGlobalKey(key, this.#globalOwner);
this.#ownedGlobalKeys.add(key);
}
#activateGlobals(action: string): void {
if (this.#disposed) throw new Error(`Cannot ${action} on a disposed JS runtime`);
activateGlobalOwner(this.#globalOwner, this.#ownedGlobalKeys, action);
}
readonly helpers: HelperBundle;
#cwd: string;
#session: { cwd: string; sessionId: string };
readonly sessionId: string;
#env: Map<string, string>;
#als = new AsyncLocalStorage<RunContext>();
#moduleLoader: LocalModuleLoader;
#localRoots: Record<string, string>;
constructor(opts: RuntimeOptions) {
this.#cwd = opts.initialCwd;
this.#session = { cwd: opts.initialCwd, sessionId: opts.sessionId };
this.sessionId = opts.sessionId;
this.#env = new Map();
this.#moduleLoader = new LocalModuleLoader(this.sessionId);View on GitHub (pinned to 9690622007)
Solutions
- Create a fresh runtime (or new eval session) instead of reusing the disposed one.
- Check disposal/liveness state before scheduling runs; drop references after dispose().
- Serialize teardown: cancel pending runs before disposing, and guard callbacks with a disposed flag.
Example fix
// before runtime.dispose(); await runtime.run(code); // throws // after if (isDisposed(runtime)) runtime = createRuntime(); await runtime.run(code);
Defensive patterns
Strategy: try-catch
Validate before calling
if (runtimeDisposed) throw new Error("runtime already disposed; create a new one"); Type guard
function isUsable(r: { [disposed]: boolean } | null | undefined): boolean {
return r != null && !r["disposed"];
} Try / catch
try {
await runtime.run(code);
} catch (err) {
if (String(err?.message).includes("on a disposed JS runtime")) {
runtime = createRuntime(); // recreate and retry once
await runtime.run(code);
} else throw err;
} Prevention
- Null out runtime references immediately after dispose().
- Cancel or drain pending runs before disposing the session.
- Add a liveness check before any queued/deferred run executes.
When it happens
Trigger: Calling `runtime.run(...)`, `runtime.setCwd(...)`, or `runtime.setRunScope(...)` after `runtime.dispose()` (or after the worker/session teardown disposed it) — e.g. a queued run completing after termination, or reusing a cached runtime object across cell teardown.
Common situations: Race between an in-flight eval run and session kill; caching a runtime in user code and reusing it in a later cell; worker restart logic invoking setCwd on the old disposed instance.
Related errors
- Cannot set cwd on a disposed JS runtime
- cmux socket closed
- DAP adapter ${this.adapter.name} is not running
- Debug session ${root.id} is still active. Terminate it befor
- write() expects string, Blob, ArrayBuffer, or TypedArray dat
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1ae4155b98dede73.
Report an issue: GitHub.