moeru-ai/airi · error

mutexAcquireTimeout must be a positive finite number

Error message

mutexAcquireTimeout must be a positive finite number

What it means

Thrown by initScreenCaptureForMain() when options.mutexAcquireTimeout is a number that is <= 0, not finite (Infinity/-Infinity), or NaN. The value is used to wrap the source-selection Mutex with withTimeout, so a non-positive or unbounded timeout would make acquisition semantics meaningless.

Source

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

let screenCaptureSourceMutexHandle: string | undefined
let setSourceMutexTimeoutHandle: NodeJS.Timeout | undefined

export function initScreenCaptureForMain(options: InitMainOptions = {}): void {
  const {
    forceCoreAudioTap = false,
    mutexAcquireTimeout = 5000,
  } = options

  let log = useLogg('screen-capture').useGlobalConfig()
  if (options?.loggerOptions?.logLevel) {
    log = log.withLogLevelString((options?.loggerOptions?.logLevel ?? 'info') as LogLevelString)
  }
  if (options?.loggerOptions?.format) {
    log = log.withFormat((options?.loggerOptions?.format ?? 'plain') as Format)
  }

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

  if (initMainCalled) {
    log.warn('initScreenCaptureForMain should only be called once')
    return
  }
  initMainCalled = true
  setSourceMutex = withTimeout(new Mutex(), mutexAcquireTimeout)

  // Get other enabled features from the command line.
  const otherEnabledFeatures = app.commandLine.getSwitchValue(featureSwitchKey)?.split(',')

  // Remove the switch if it exists.
  if (app.commandLine.hasSwitch(featureSwitchKey)) {
    app.commandLine.removeSwitch(featureSwitchKey)
  }

  // Add the feature flags to the command line with any other user-enabled features concatenated.

View on GitHub (pinned to 27111382b4)

Solutions

  1. Omit the option to accept the 5000ms default, or pass a positive finite number such as 5000.
  2. Coerce and validate config-sourced values: const t = Number(raw); if (!(t > 0 && Number.isFinite(t))) use default.
  3. Reject non-numeric config at the boundary instead of forwarding it to initScreenCaptureForMain.

Example fix

// before
initScreenCaptureForMain({ mutexAcquireTimeout: Number(process.env.SCREENCAP_TIMEOUT) })
// after
const raw = Number(process.env.SCREENCAP_TIMEOUT)
initScreenCaptureForMain({ mutexAcquireTimeout: Number.isFinite(raw) && raw > 0 ? raw : 5000 })
Defensive patterns

Strategy: validation

Validate before calling

function resolveMutexTimeout(raw: unknown): number {
  const n = typeof raw === 'number' ? raw : Number(raw)
  if (!(Number.isFinite(n) && n > 0)) return 5000 // default
  return n
}

initScreenCaptureForMain({ mutexAcquireTimeout: resolveMutexTimeout(configValue) })

Type guard

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

Prevention

When it happens

Trigger: Passing mutexAcquireTimeout: 0, a negative number, Infinity, or NaN in InitMainOptions; passing a value read from env/JSON config without coercion that ends up as NaN.

Common situations: Config file with mutexAcquireTimeout: 0 intending 'no wait'; env var parsed with Number('') producing 0; JSON config with a string that Number()-coerces to NaN; copy-paste of a default that was later changed.

Understand the failure class

Related errors


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