{"record":{"id":"989f9437d20c2023","repo":"moeru-ai/airi","slug":"computer-use-has-stopped","errorCode":null,"errorMessage":"Computer use has stopped.","messagePattern":"Computer use has stopped\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/airi/computer-use/index.ts","lineNumber":19,"sourceCode":"import type { createContext } from '@moeru/eventa/adapters/electron/main'\n\nimport { join, sep } from 'node:path'\n\nimport { binaryPath } from '@auv-js/cli/binary'\nimport { defineInvokeHandler } from '@moeru/eventa'\nimport { app } from 'electron'\n\nimport { computerUseReadImage, computerUseRun } from '../../../../shared/eventa/computer-use'\nimport { onAppBeforeQuit } from '../../../libs/bootkit/lifecycle'\nimport { createComputerUseRuntime } from './runtime'\n\n/** Registers host-owned computer use. The executable is resolved only on the first call. */\nexport function setupComputerUse(context: ReturnType<typeof createContext>['context']) {\n  let runtime: ReturnType<typeof createComputerUseRuntime> | undefined\n  let stopped = false\n  function getRuntime() {\n    if (stopped)\n      throw new Error('Computer use has stopped.')\n    if (!runtime) {\n      const executable = binaryPath()\n      runtime = createComputerUseRuntime({\n        // Native executables cannot run inside Electron's archive.\n        binaryPath: app.isPackaged ? executable.replace(`${sep}app.asar${sep}`, `${sep}app.asar.unpacked${sep}`) : executable,\n        storeRoot: join(app.getPath('userData'), 'computer-use'),\n      })\n    }\n    return runtime\n  }\n  defineInvokeHandler(context, computerUseRun, input => getRuntime().run(input))\n  defineInvokeHandler(context, computerUseReadImage, input => getRuntime().readImage(input))\n  onAppBeforeQuit(async () => {\n    stopped = true\n    await runtime?.dispose()\n  })\n}\n","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/apps/stage-tamagotchi/src/main/services/airi/computer-use/index.ts#L1-L37","documentation":"setupComputerUse lazily creates the computer-use runtime on first use. getRuntime throws this error when the host previously called dispose() and the service is marked stopped, so any later caller cannot resurrect the runtime. It exists to fail fast instead of spawning a daemon after the feature was shut down.","triggerScenarios":"Calling any computer-use operation (via run or readImage through getRuntime) after setupComputerUse's runtime was disposed with dispose(), which sets stopped = true.","commonSituations":"App quit/shutdown ordering where a pending AI tool call arrives after dispose; hot-reload of the main process leaving a stale handler; a window requesting a screenshot after the user disabled computer use.","solutions":["Re-invoke setupComputerUse to create a fresh runtime before any further calls","Remove or cancel queued tool calls when the service is disposed","Check the stopped state (or track dispose in caller state) before dispatching computer-use commands"],"exampleFix":"// before\nawait computerUse.run({ argv: ['invoke', 'screen.capture'] }) // after stop\n// after\nif (!computerUse.isStopped()) {\n  await computerUse.setupComputerUse(context)\n  await computerUse.run({ argv: ['invoke', 'screen.capture'] })\n}","handlingStrategy":"try-catch","validationCode":"// Track service state in the caller\nlet computerUseDisposed = false\nfunction canUseComputerUse() { return !computerUseDisposed }","typeGuard":"function isRuntimeAvailable(rt: ReturnType<typeof createComputerUseRuntime> | undefined): rt is ReturnType<typeof createComputerUseRuntime> { return rt !== undefined }","tryCatchPattern":"try {\n  await computerUse.run(input)\n} catch (e) {\n  if (e instanceof Error && e.message === 'Computer use has stopped.') {\n    return { status: 'cancelled' } // do not retry; re-setup is required\n  }\n  throw e\n}","preventionTips":["Set computerUseDisposed = true inside your dispose wrapper and check it before dispatching commands","Never hold long-lived references to the runtime across app-shutdown boundaries","Cancel in-flight AI tool calls in the same lifecycle step that calls dispose"],"tags":["lifecycle","electron","disposed-runtime"],"backgroundTag":"invalid-state-transition","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-17T01:14:42.644Z","contentChangedAt":"2026-09-17T01:14:42.644Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}