{"record":{"id":"351b7e3ca0d3a005","repo":"moeru-ai/airi","slug":"computer-use-has-stopped-runtime","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/runtime.ts","lineNumber":77,"sourceCode":"      return started\n    }\n    catch (error) {\n      await cleanupSocket()\n      throw error\n    }\n  }\n\n  async function cleanupSocket() {\n    if (socketDirectory) {\n      await rm(socketDirectory, { recursive: true, force: true })\n      socketDirectory = undefined\n    }\n  }\n\n  function enqueue<T>(operation: () => Promise<T>): Promise<T> {\n    const next = queue.then(() => {\n      if (disposed)\n        throw new Error('Computer use has stopped.')\n      return operation()\n    })\n    // A failed command must not prevent later commands from running.\n    queue = next.then(() => undefined, () => undefined)\n    return next\n  }\n\n  async function run(input: unknown): Promise<ComputerUseResult> {\n    const { argv } = v.parse(inputSchema, input)\n    if (argv[0] !== 'invoke' || !/^(?:--help|-h|help|(?:app|display|screen|window|input|mediaControl)\\.[a-zA-Z]+)$/.test(argv[1]))\n      throw new Error('Use invoke with a desktop command or --help.')\n\n    // The host owns routing and artifact storage. CLI global flags must not replace them.\n    const managedFlags = ['--store-root', '--endpoint', '--device', '--device-id', '--run', '--config', '--profile', '--discovery-file', '--pairing-store']\n    if (argv.some(arg => managedFlags.some(flag => arg === flag || arg.startsWith(`${flag}=`))))\n      throw new Error('Computer use routing and storage are managed by AIRI.')\n\n    return enqueue(async () => {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/apps/stage-tamagotchi/src/main/services/airi/computer-use/runtime.ts#L59-L95","documentation":"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.","triggerScenarios":"Calling run() or readImage() while a prior operation is still in flight and dispose() runs before the queued operation's turn arrives.","commonSituations":"User quits/disables computer use while a long screenshot request is queued; dispose() during shutdown races pending AI tool calls.","solutions":["Await in-flight run/readImage promises before calling dispose","Guard callers with a stopped/cancelled check before enqueueing","Catch this error and treat it as a cancellation, not a command failure"],"exampleFix":"// before\nconst result = await runtime.run(input) // may be disposed mid-queue\n// after\ntry {\n  const result = await runtime.run(input)\n} catch (e) {\n  if (e.message.includes('has stopped')) return cancelledResult\n  throw e\n}","handlingStrategy":"retry","validationCode":"// Wait for queued work before disposing\nawait Promise.race([pendingOperations, timeout(5000)])","typeGuard":null,"tryCatchPattern":"try {\n  return await runtime.run(input)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Computer use has stopped')) {\n    // treat as cancellation; optionally re-setup and retry once\n    return cancelled\n  }\n  throw e\n}","preventionTips":["Serialize all run/readImage calls through a single owner so you know when the queue is empty","Await all in-flight promises before dispose","On shutdown, first reject new requests, then drain, then dispose"],"tags":["lifecycle","race-condition","queue"],"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"}