{"record":{"id":"2fc65568faf1d91a","repo":"pydantic/monty","slug":"the-pool-is-closed-create-a-new-monty-pool","errorCode":null,"errorMessage":"the pool is closed — create a new Monty pool","messagePattern":"the pool is closed — create a new Monty pool","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/pool.ts","lineNumber":153,"sourceCode":"      ...(options.requestTimeout !== undefined ? { requestTimeoutMs: options.requestTimeout * 1000 } : {}),\n      // `null` disables the backstop; omitted means the 1s default\n      ...(options.durationLimitGrace !== null\n        ? { durationLimitGraceMs: (options.durationLimitGrace ?? 1) * 1000 }\n        : {}),\n      ...(options.maxCheckoutsPerWorker !== undefined ? { maxCheckoutsPerWorker: options.maxCheckoutsPerWorker } : {}),\n    })\n    await native.start()\n    return new Monty(native)\n  }\n\n  /**\n   * Checks a worker out of the pool (spawning one if allowed) and creates a\n   * REPL session in it. Release the worker with `session.close()` (or\n   * `await using`).\n   */\n  async checkout(options: CheckoutOptions = {}): Promise<MontySession> {\n    if (this.closed) {\n      throw new Error('the pool is closed — create a new Monty pool')\n    }\n    const assertAnnotations = encodeAssertMessageAnnotations(options.assertMessageAnnotations)\n    const native = this.native.checkout({\n      scriptName: options.scriptName ?? 'main.py',\n      ...(options.limits !== undefined ? { limits: options.limits } : {}),\n      typeCheck: options.typeCheck ?? false,\n      ...(options.typeCheckStubs !== undefined ? { typeCheckStubs: options.typeCheckStubs } : {}),\n      ...(options.typeCheckFormat !== undefined ? { typeCheckFormat: options.typeCheckFormat } : {}),\n      ...(options.typeCheckColor !== undefined ? { typeCheckColor: options.typeCheckColor } : {}),\n      ...(assertAnnotations !== undefined ? { assertMessageAnnotations: assertAnnotations } : {}),\n      ...(options.printFlushInterval !== undefined ? { printFlushIntervalMs: options.printFlushInterval * 1000 } : {}),\n    })\n    const telemetryContext = captureTelemetryContext()\n    await native.enter(telemetryContext)\n    return new MontySession(native)\n  }\n\n  /**","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/pool.ts#L135-L171","documentation":"Thrown by `Monty.checkout()` when `pool.close()` (or an equivalent teardown, e.g. `await using` disposal) has already ended the pool's native worker set. The pool object still exists but no longer accepts checkout requests; any subsequent session request cannot be served. The fix is to create a fresh pool via `Monty.create()` — the error is terminal for that pool instance, not transient.","triggerScenarios":"Calling `checkout` after `await pool.close()`, after an `await using pool` block exits, or retaining a pool reference (e.g. in a module-level variable or closure) past its disposal and calling checkout later.","commonSituations":"Lifetimes: a long-lived singleton pool disposed on shutdown, a callback firing after the `await using` scope ended, retry logic reusing a pool created inside a request handler that already returned.","solutions":["Create a new pool with `Monty.create(...)` before checking out","Reorder code so all checkouts happen inside the pool's `await using` block","Store a `closed`/disposed flag alongside the pool and gate callers on it"],"exampleFix":"// before\nawait using pool = await Monty.create()\nawait pool.checkout()\n// after block exits, pool is closed:\nawait pool.checkout() // throws\n// after\nconst pool = await Monty.create() // plain create, explicit close when done\nconst session = await pool.checkout()","handlingStrategy":"try-catch","validationCode":"if (pool.isClosed /* or track disposal yourself */) {\n  pool = await Monty.create(poolOptions)\n}","typeGuard":"function isUsablePool<T extends { closed: boolean }>(pool: T): boolean {\n  return !pool.closed\n}","tryCatchPattern":"try {\n  session = await pool.checkout(opts)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('the pool is closed')) {\n    pool = await Monty.create(poolOptions)\n    session = await pool.checkout(opts)\n  } else throw e\n}","preventionTips":["Keep all checkouts inside the `await using pool` scope","Never stash pools in globals or long-lived closures that outlive disposal","Wrap pool lifetime in a manager that recreates the pool on demand"],"tags":["typescript","lifecycle","pool"],"backgroundTag":"invalid-state-transition","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}