{"record":{"id":"e5319b3c98a24e4b","repo":"paperclipai/paperclip","slug":"acpx-runtime-host-is-closing","errorCode":null,"errorMessage":"ACPX runtime host is closing","messagePattern":"ACPX runtime host is closing","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/runtime-host.ts","lineNumber":603,"sourceCode":"  }\n\n  goalSnapshot(): AcpxRuntimeGoalSnapshot | null {\n    return this.#runtime.goalSnapshot?.() ?? null;\n  }\n\n  async controlGoal(\n    action: \"set\" | \"pause\" | \"resume\" | \"clear\",\n    objective?: string,\n  ): Promise<AcpxRuntimeGoalSnapshot | null> {\n    if (!this.#runtime.controlGoal) {\n      throw new Error(\"ACPX runtime does not expose session goal controls\");\n    }\n    return await this.#runtime.controlGoal(action, objective);\n  }\n\n  startTurn(input: AcpxRuntimeTurnInput): AcpxRuntimeTurn {\n    if (this.#closed || this.#closingStarted) {\n      throw new Error(\"ACPX runtime host is closing\");\n    }\n    if (this.#activeTurn) {\n      throw new Error(\"ACPX runtime host already has an active turn\");\n    }\n    const requestId = boundedRequestId(input.requestId);\n    const text = boundedTurnText(input.text);\n    const turn = this.#runtime.startTurn({\n      text,\n      requestId,\n      ...(input.signal ? { signal: input.signal } : {}),\n      ...(input.onElicitation ? { onElicitation: input.onElicitation } : {}),\n    });\n    this.#activeTurn = turn;\n    void turn.result\n      .finally(() => {\n        // Once shutdown owns this turn, retain its cancellation handle until\n        // runtime cleanup succeeds. The result may settle while cleanup is\n        // failing, and a later close must still be able to retry cancellation.","sourceCodeStart":585,"sourceCodeEnd":621,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/runtime-host.ts#L585-L621","documentation":"startTurn() on the ACPX runtime host is refused once the host has been closed or a close sequence has begun (#closed or #closingStarted). The host is single-lifetime: after close() no new turns may be started.","triggerScenarios":"Calling startTurn() after close() was called, after an async close began (closingStarted set even if teardown is still in flight), or concurrently racing a close that started between an earlier availability check and the startTurn call.","commonSituations":"A request arrives while the session is shutting down; retry logic re-issues startTurn after the host already closed due to an earlier failure; lifecycle race where a scheduler keeps submitting turns to a closing host.","solutions":["Check the host's closed/closing state (or track it via close events) before calling startTurn","Reopen a new runtime host via open() and submit the turn there","Fix lifecycle management so no turn submissions are queued after close() is initiated","Guard retries: on shutdown, drop pending turns instead of re-submitting"],"exampleFix":"// before\nhost.close();\nhost.startTurn({ text: \"hi\" }); // throws\n// after\nif (!host.isClosed && !host.isClosing) {\n  host.startTurn({ text: \"hi\" });\n} else {\n  const fresh = await open({ /* same options */ });\n  fresh.startTurn({ text: \"hi\" });\n}","handlingStrategy":"try-catch","validationCode":"function canStartTurn(host) {\n  return !host.isClosed && !host.isClosing;\n}","typeGuard":"function isOpenForTurns(host) {\n  return \"isClosed\" in host && host.isClosed === false && host.isClosingStarted === false;\n}","tryCatchPattern":"try {\n  host.startTurn({ text });\n} catch (err) {\n  if (err.message === \"ACPX runtime host is closing\") {\n    const fresh = await open(options);\n    return fresh.startTurn({ text });\n  }\n  throw err;\n}","preventionTips":["Stop turn producers before calling close()","Track host lifecycle state in your scheduler","Never retry startTurn on a host known to be closing","Route post-shutdown requests to a freshly opened host"],"tags":["acpx","runtime","lifecycle","closed-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}