moeru-ai/airi · error · Error
Computer use has stopped.
Error message
Computer use has stopped.
What it means
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.
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
Example fix
// before
await computerUse.run({ argv: ['invoke', 'screen.capture'] }) // after stop
// after
if (!computerUse.isStopped()) {
await computerUse.setupComputerUse(context)
await computerUse.run({ argv: ['invoke', 'screen.capture'] })
} Defensive patterns
Strategy: try-catch
Validate before calling
// Track service state in the caller
let computerUseDisposed = false
function canUseComputerUse() { return !computerUseDisposed } Type guard
function isRuntimeAvailable(rt: ReturnType<typeof createComputerUseRuntime> | undefined): rt is ReturnType<typeof createComputerUseRuntime> { return rt !== undefined } Try / catch
try {
await computerUse.run(input)
} catch (e) {
if (e instanceof Error && e.message === 'Computer use has stopped.') {
return { status: 'cancelled' } // do not retry; re-setup is required
}
throw e
} Prevention
- 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
When it happens
Trigger: Calling any computer-use operation (via run or readImage through getRuntime) after setupComputerUse's runtime was disposed with dispose(), which sets stopped = true.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- The Extension management window is no longer available.
- Apple Speech transcription requires the Electron desktop…
- Chat session was removed before send completed
- checkMacOSScreenCapturePermission is only available on…
- Computer use has stopped.
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/989f9437d20c2023.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/computer-use/index.ts:19
import type { createContext } from '@moeru/eventa/adapters/electron/main'
import { join, sep } from 'node:path'
import { binaryPath } from '@auv-js/cli/binary'
import { defineInvokeHandler } from '@moeru/eventa'
import { app } from 'electron'
import { computerUseReadImage, computerUseRun } from '../../../../shared/eventa/computer-use'
import { onAppBeforeQuit } from '../../../libs/bootkit/lifecycle'
import { createComputerUseRuntime } from './runtime'
/** Registers host-owned computer use. The executable is resolved only on the first call. */
export function setupComputerUse(context: ReturnType<typeof createContext>['context']) {
let runtime: ReturnType<typeof createComputerUseRuntime> | undefined
let stopped = false
function getRuntime() {
if (stopped)
throw new Error('Computer use has stopped.')
if (!runtime) {
const executable = binaryPath()
runtime = createComputerUseRuntime({
// Native executables cannot run inside Electron's archive.
binaryPath: app.isPackaged ? executable.replace(`${sep}app.asar${sep}`, `${sep}app.asar.unpacked${sep}`) : executable,
storeRoot: join(app.getPath('userData'), 'computer-use'),
})
}
return runtime
}
defineInvokeHandler(context, computerUseRun, input => getRuntime().run(input))
defineInvokeHandler(context, computerUseReadImage, input => getRuntime().readImage(input))
onAppBeforeQuit(async () => {
stopped = true
await runtime?.dispose()
})
}
View on GitHub (pinned to 438a067dde)