{"record":{"id":"fa8ca6a72ba162f7","repo":"can1357/oh-my-pi","slug":"cannot-set-cwd-on-a-disposed-js-runtime","errorCode":null,"errorMessage":"Cannot set cwd on a disposed JS runtime","messagePattern":"Cannot set cwd on a disposed JS runtime","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/js/shared/runtime.ts","lineNumber":217,"sourceCode":"\t\tthis.sessionId = opts.sessionId;\n\t\tthis.#env = new Map();\n\t\tthis.#moduleLoader = new LocalModuleLoader(this.sessionId);\n\t\tthis.#localRoots = opts.localRoots ?? {};\n\t\tthis.helpers = createHelpers({\n\t\t\tcwd: () => this.#activeCwd(),\n\t\t\tenv: this.#env,\n\t\t\tlocalRoots: () => this.#localRoots,\n\t\t\temitStatus: event => this.#activeHooks(\"emitStatus\")?.onDisplay({ type: \"status\", event }),\n\t\t});\n\t\tthis.#install(opts.extraGlobals);\n\t}\n\n\tget cwd(): string {\n\t\treturn this.#cwd;\n\t}\n\n\tsetCwd(cwd: string): void {\n\t\tif (this.#disposed) throw new Error(\"Cannot set cwd on a disposed JS runtime\");\n\t\t// Always stamp the runtime and session state: WorkerCore/browser/cmux call\n\t\t// setCwd from init and pre-run paths that may race another same-realm\n\t\t// runtime, and a throw here used to escape the inline-worker microtask\n\t\t// path as a fatal unhandledRejection that killed the whole session.\n\t\t// #session is the same object saved in this owner's global stack entry,\n\t\t// so the new cwd survives deferred activation and is visible to this\n\t\t// runtime's next run; run()/setRunScope still assert exclusive ownership.\n\t\tthis.#cwd = cwd;\n\t\tthis.#session.cwd = cwd;\n\t\tif (activeGlobalRunOwner === null || activeGlobalRunOwner === this.#globalOwner) {\n\t\t\tthis.#activateGlobals(\"set cwd\");\n\t\t}\n\t}\n\n\t/**\n\t * Install per-run globals. Intended for run-scoped state (browser's `tab`, `display`\n\t * overrides, etc.). Overwrites previous assignments — caller is responsible for any\n\t * cleanup it wants.","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/js/shared/runtime.ts#L199-L235","documentation":"`setCwd` explicitly checks `#disposed` and throws `Cannot set cwd on a disposed JS runtime`. The comment documents why the throw is deliberate but guarded: WorkerCore/browser/cmux call setCwd from init/pre-run paths that can race another same-realm runtime, and an escaping throw used to become a fatal unhandledRejection that killed the whole session. Callers must treat this as a signal to recreate the runtime.","triggerScenarios":"Calling `runtime.setCwd(newCwd)` after the runtime was disposed — typically from an inline-worker init or pre-run path racing session teardown, or deferred activation code re-stamping cwd on a dead runtime.","commonSituations":"Same-realm worker fallback where two runtimes share a global and teardown of one disposes the other's cwd stamp; asynchronous init resolving after the session was killed; session cwd change events delivered post-dispose.","solutions":["Guard the setCwd call with a disposed/liveness check on the runtime before invoking it.","Wrap init/pre-run cwd updates in try-catch and treat disposal as a no-op (abort the stale path) rather than a fatal error.","Recreate the runtime and re-apply the cwd to the new instance if the cwd change must take effect."],"exampleFix":"// before\nruntime.setCwd(newCwd); // may throw fatal unhandledRejection if disposed\n// after\ntry {\n\truntime.setCwd(newCwd);\n} catch (err) {\n\tif (!(err instanceof Error && err.message.includes(\"disposed\"))) throw err;\n\t// stale runtime: drop it; next run creates a fresh one with the current cwd\n}","handlingStrategy":"try-catch","validationCode":"if (runtimeDisposed) return; // skip stale cwd stamp","typeGuard":null,"tryCatchPattern":"try {\n\truntime.setCwd(cwd);\n} catch (err) {\n\tif (!(err instanceof Error && err.message === \"Cannot set cwd on a disposed JS runtime\")) throw err;\n\t// stale runtime raced teardown: drop it; next run recreates with current cwd\n}","preventionTips":["Guard init/pre-run cwd updates with the runtime's disposed flag.","Deliver cwd changes through the session owner so disposed runtimes are skipped, not stamped.","Never fire setCwd from async callbacks that can resolve after session teardown."],"tags":["lifecycle","runtime","cwd","race-condition"],"backgroundTag":"set-cwd-after-dispose","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}