moeru-ai/airi · error

timeout must be a positive finite number

Error message

timeout must be a positive finite number

What it means

Thrown by the screenCapture.setSource invoke handler when request.timeout is a number but is <= 0, not finite, or NaN. Only numeric values are validated; undefined/string timeouts are passed through untouched. The timeout governs how long the source-selection mutex is held before auto-release.

Source

Thrown at packages/electron-screen-capture/src/main/index.ts:226

  defineInvokeHandler(context, screenCapture.getSources, async (sourcesOptions) => {
    // NOTICE(@nekomeowww): In probability of 9/10, the window thumbnail is purely empty or black, sources printed and
    // nothing is returned from the desktopCapturer API.
    // NOTICE(@sumimakito): Not only thumbnail is empty, the appIcon could be empty as well with nothing returned.
    // REVIEW(@sumimakito): This has nothing to do with out side, probably related to Electron Bug, you can
    // read more here https://github.com/electron/electron/issues/44504
    const sources = await desktopCapturer.getSources(sourcesOptions)
    return sources.map(source => toSerializableDesktopCapturerSource(source))
  })

  defineInvokeHandler(context, screenCapture.setSource, async (request, eventaOptions) => {
    // FIXME: Would be better if `onlySameWindow` in `createContext` also filters out invocations here.
    if (window.webContents.id !== eventaOptions?.raw.ipcMainEvent.sender.id)
      return

    const { timeout } = request
    if (typeof timeout === 'number' && (timeout <= 0 || !Number.isFinite(timeout) || Number.isNaN(timeout))) {
      throw new Error('timeout must be a positive finite number')
    }

    await setSourceMutex.acquire()
    log.withFields({ windowId, windowTitle: tryWindowTitle(window, windowTitle) }).debug('setSourceMutex acquired')

    clearTimeout(setSourceMutexTimeoutHandle)
    const handle = nanoid()
    setSourceMutexTimeoutHandle = undefined
    screenCaptureSourceMutexHandle = handle

    try {
      session.setDisplayMediaRequestHandler(async (_request, callback) => {
        const sources = await desktopCapturer.getSources(request.options)
        const source = sources.find(source => source.id === request.sourceId)
        if (!source) {
          throw new Error(`Source with id ${request.sourceId} not found.`)
        }

View on GitHub (pinned to 27111382b4)

Solutions

  1. Omit timeout if you do not need a custom value, or pass a positive finite number of milliseconds.
  2. Validate renderer-side before invoking: if provided, ensure Number.isFinite(t) && t > 0.
  3. Default the UI control to a sensible minimum (e.g. 30000) and disable submit at 0.

Example fix

// before (renderer)
invoke(setSource, { sourceId, options, timeout: 0 })
// after
invoke(setSource, { sourceId, options, timeout: 30000 })
Defensive patterns

Strategy: validation

Validate before calling

function normalizeTimeout(t: unknown): number | undefined {
  if (t === undefined || typeof t === 'string') return t as undefined
  if (typeof t === 'number' && Number.isFinite(t) && t > 0) return t
  throw new Error('timeout must be a positive finite number')
}

Type guard

function isValidTimeout(t: unknown): t is number {
  return typeof t === 'number' && Number.isFinite(t) && t > 0
}

Try / catch

try {
  await invoke(screenCapture.setSource, { sourceId, options, timeout })
} catch (error) {
  if (error instanceof Error && error.message.includes('positive finite number')) {
    // drop timeout and retry with default
    await invoke(screenCapture.setSource, { sourceId, options })
  } else throw error
}

Prevention

When it happens

Trigger: A renderer invoking setSource({ sourceId, options, timeout: 0 }) or timeout: -1, Infinity, NaN; a UI control that computes timeout from an empty input that coerces to 0.

Common situations: Renderer passing timeout: 0 intending 'instant'; a slider bound to a number that can go to 0; JSON payload from a third-party caller with timeout as a malformed number.

Understand the failure class

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/b055fa357d02be75. Report an issue: GitHub.