{"record":{"id":"7cc8c6d58d5606ab","repo":"slopus/happy","slug":"backend-has-been-disposed","errorCode":null,"errorMessage":"Backend has been disposed","messagePattern":"Backend has been disposed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/happy-cli/src/agent/acp/AcpBackend.ts","lineNumber":376,"sourceCode":"    if (index !== -1) {\n      this.listeners.splice(index, 1);\n    }\n  }\n\n  private emit(msg: AgentMessage): void {\n    if (this.disposed) return;\n    for (const listener of this.listeners) {\n      try {\n        listener(msg);\n      } catch (error) {\n        logger.warn('[AcpBackend] Error in message handler:', error);\n      }\n    }\n  }\n\n  async startSession(initialPrompt?: string): Promise<StartSessionResult> {\n    if (this.disposed) {\n      throw new Error('Backend has been disposed');\n    }\n\n    const sessionId = randomUUID();\n    this.emit({ type: 'status', status: 'starting' });\n    let startupStatusErrorEmitted = false;\n\n    try {\n      logger.debug(`[AcpBackend] Starting session: ${sessionId}`);\n      // Spawn the ACP agent process\n      const args = this.options.args || [];\n      \n      // On Windows, spawn via cmd.exe to handle .cmd files and PATH resolution\n      // This ensures proper stdio piping without shell buffering\n      if (process.platform === 'win32') {\n        const fullCommand = [this.options.command, ...args].join(' ');\n        this.process = spawn('cmd.exe', ['/c', fullCommand], {\n          cwd: this.options.cwd,\n          env: { ...process.env, ...this.options.env },","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/agent/acp/AcpBackend.ts#L358-L394","documentation":"AcpBackend.startSession() checks the `disposed` flag before creating a new ACP session. Once `dispose()` has been called (e.g. process exit, CLI shutdown, or manual teardown), the backend is permanently unusable and any attempt to start a session throws this error instead of spawning the agent process. It exists to prevent use-after-free on killed child processes and closed connections.","triggerScenarios":"Calling startSession() after dispose() was invoked on the AcpBackend instance; reusing a cached backend across sessions after shutdown; a race where an async caller starts a session while the CLI is tearing the backend down.","commonSituations":"Reusing a stored AcpBackend reference after the CLI terminated an agent; retry logic that retries startSession after a failed/disposed session; long-lived wrappers that keep the backend in a map and a concurrent shutdown disposes it.","solutions":["Check `backend.disposed` (or track your own disposed flag) before calling startSession and create a fresh AcpBackend instead.","Replace the disposed backend with a new instance and retry once, rather than retrying on the same instance.","Audit code paths that call dispose() (signal handlers, process exit) to ensure no in-flight startSession callers remain."],"exampleFix":"// before\nawait cachedBackend.startSession(prompt);\n// after\nif (cachedBackend.disposed) {\n  cachedBackend = new AcpBackend(config);\n}\nawait cachedBackend.startSession(prompt);","handlingStrategy":"try-catch","validationCode":"if (backend.disposed) {\n  backend = new AcpBackend(config);\n}\nawait backend.startSession(prompt);","typeGuard":"function isUsable(b: AcpBackend): b is AcpBackend & { disposed: false } {\n  return !b.disposed;\n}","tryCatchPattern":"try {\n  await backend.startSession(prompt);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Backend has been disposed') {\n    backend = new AcpBackend(config);\n    await backend.startSession(prompt);\n  } else throw err;\n}","preventionTips":["Always check the disposed flag before reusing a cached backend.","Centralize backend lifecycle in one owner that invalidates references on dispose.","Never retry startSession on the same instance; recreate instead.","Cancel prompt producers before calling dispose()."],"tags":["lifecycle","disposed","acp","state"],"backgroundTag":"use-after-dispose","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}