moeru-ai/airi · error · Error
Use invoke with a desktop command or --help.
Error message
Use invoke with a desktop command or --help.
What it means
run() validates the parsed argv against the small supported surface: it must start with 'invoke' followed by --help/-h/help or a command of the form app.*/display.*/screen.*/window.*/input.*/mediaControl.*. Anything else is rejected because the host only exposes a curated subset of the computer-use CLI.
Solutions
- Use argv = ['invoke', '<command>'] where command matches app.*/display.*/screen.*/window.*/input.*/mediaControl.* or --help/-h/help
- Check the exposed command list in the runtime/CLI help before constructing argv
- Validate the LLM-generated command against the regex before calling run
Example fix
// before
await run({ argv: ['devices', 'list'] })
// after
await run({ argv: ['invoke', 'app.list'] }) Defensive patterns
Strategy: validation
Validate before calling
const COMMAND_RE = /^(?:--help|-h|help|(?:app|display|screen|window|input|mediaControl)\.[a-zA-Z]+)$/nfunction isValidInvoke(argv: unknown[]): boolean {
return argv[0] === 'invoke' && COMMAND_RE.test(String(argv[1]))
} Type guard
function isComputerUseArgv(v: unknown): v is ['invoke', string, ...string[]] {
return Array.isArray(v) && v[0] === 'invoke' && typeof v[1] === 'string'
&& /^(?:--help|-h|help|(?:app|display|screen|window|input|mediaControl)\.[a-zA-Z]+)$/.test(v[1])
} Try / catch
try {
return await run({ argv })
} catch (e) {
if (e instanceof Error && e.message.startsWith('Use invoke with a desktop command')) {
console.warn(`Unsupported command: ${argv.join(' ')}; see help for allowed commands`)
return helpResult
}
throw e
} Prevention
- Keep a whitelist of allowed commands and generate argv from it, not from free-form strings
- Sanitize LLM tool-call output against the same regex the runtime uses
- Prefer calling --help first to discover the supported command surface
When it happens
Trigger: Passing argv[0] other than 'invoke', an unknown command namespace (e.g. 'browser.open'), or a bare subcommand name not matching the regex as argv[1].
Common situations: Copy-pasting CLI examples from upstream docs that use subcommands AIRI does not expose; typos like 'invok'; passing empty or malformed argv from an LLM tool call.
Understand the failure class
Background: "Unknown argument", "Invalid value", and "must be one of": invalid CLI argument errors explained — this error's family across 35 libraries.
Related errors
- A motion sample occurs after the recording duration.
- Aliyun NLS credentials are incomplete.
- Archive entry not found
- audio models upstream returned malformed body
- audio voices upstream returned malformed body
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/bbc475cabc93362b.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/computer-use/runtime.ts:88
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 () => {
const active = await connect()
const command = [...argv]
if (!command.includes('--json') && !command.includes('--help') && !command.includes('-h') && command[1] !== 'help')
command.push('--json')
command.push('--store-root', options.storeRoot)
let stdout: string
let stderr: string
let exitCode = 0
try {
({ stdout, stderr } = await execFileAsync(options.binaryPath, command, {
env: { ...process.env, AUV_ENDPOINT: active.connectionOptions.endpoint, AUV_CONTEXT: undefined },View on GitHub (pinned to 438a067dde)