moeru-ai/airi · error · Error

Computer use has stopped.

Error message

Computer use has stopped.

What it means

enqueue serializes operations on a promise queue. Each queued operation re-checks the disposed flag just before running, so commands accepted before dispose but not yet executed still fail with this error instead of hitting a dead daemon.

Solutions

  1. Await in-flight run/readImage promises before calling dispose
  2. Guard callers with a stopped/cancelled check before enqueueing
  3. Catch this error and treat it as a cancellation, not a command failure

Example fix

// before
const result = await runtime.run(input) // may be disposed mid-queue
// after
try {
  const result = await runtime.run(input)
} catch (e) {
  if (e.message.includes('has stopped')) return cancelledResult
  throw e
}
Defensive patterns

Strategy: retry

Validate before calling

// Wait for queued work before disposing
await Promise.race([pendingOperations, timeout(5000)])

Try / catch

try {
  return await runtime.run(input)
} catch (e) {
  if (e instanceof Error && e.message.includes('Computer use has stopped')) {
    // treat as cancellation; optionally re-setup and retry once
    return cancelled
  }
  throw e
}

Prevention

When it happens

Trigger: Calling run() or readImage() while a prior operation is still in flight and dispose() runs before the queued operation's turn arrives.

Common situations: User quits/disables computer use while a long screenshot request is queued; dispose() during shutdown races pending AI tool calls.

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


AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17). Data as JSON: /api/errors/351b7e3ca0d3a005. Report an issue: GitHub.

Appendix: source

Thrown at apps/stage-tamagotchi/src/main/services/airi/computer-use/runtime.ts:77

      return started
    }
    catch (error) {
      await cleanupSocket()
      throw error
    }
  }

  async function cleanupSocket() {
    if (socketDirectory) {
      await rm(socketDirectory, { recursive: true, force: true })
      socketDirectory = undefined
    }
  }

  function enqueue<T>(operation: () => Promise<T>): Promise<T> {
    const next = queue.then(() => {
      if (disposed)
        throw new Error('Computer use has stopped.')
      return operation()
    })
    // A failed command must not prevent later commands from running.
    queue = next.then(() => undefined, () => undefined)
    return next
  }

  async function run(input: unknown): Promise<ComputerUseResult> {
    const { argv } = v.parse(inputSchema, input)
    if (argv[0] !== 'invoke' || !/^(?:--help|-h|help|(?:app|display|screen|window|input|mediaControl)\.[a-zA-Z]+)$/.test(argv[1]))
      throw new Error('Use invoke with a desktop command or --help.')

    // The host owns routing and artifact storage. CLI global flags must not replace them.
    const managedFlags = ['--store-root', '--endpoint', '--device', '--device-id', '--run', '--config', '--profile', '--discovery-file', '--pairing-store']
    if (argv.some(arg => managedFlags.some(flag => arg === flag || arg.startsWith(`${flag}=`))))
      throw new Error('Computer use routing and storage are managed by AIRI.')

    return enqueue(async () => {

View on GitHub (pinned to 438a067dde)