moeru-ai/airi · error · Error

Source with id not found.

Error message

Source with id ${request.sourceId} not found.

What it means

Thrown inside the session.setDisplayMediaRequestHandler callback registered by screenCapture.setSource when desktopCapturer.getSources(request.options) returns no source whose id equals request.sourceId. The sourceId must be one of the ids returned by a prior screenCapture.getSources call, and the same enumeration options matter because they can change which sources Electron reports. Since this fires during the renderer's getUserMedia call, the error surfaces as a failed getDisplayMedia media request. Note the handler stays installed with the mutex held until the timeout handle fires or resetSource is called.

Solutions

  1. Re-enumerate sources with screenCapture.getSources immediately before setSource and use a fresh id from that result.
  2. Make sure request.options passed to setSource match the options used for the getSources call that produced sourceId.
  3. In the renderer, catch the getUserMedia/getDisplayMedia failure, re-run getSources, and let the user (or a heuristic like same title) re-pick.
  4. If the window was closed or the display removed, refresh your source list UI instead of reusing stale ids.

Example fix

// before
const [source] = sourcesFromStartup // stale list
await invoke(screenCapture.setSource, { sourceId: source.id, options })
const stream = await navigator.mediaDevices.getUserMedia({ video: true })

// after
const sources = await invoke(screenCapture.getSources, options)
const source = sources.find(s => s.id === wantedId) ?? sources.find(s => s.name === wantedName)
if (!source) throw new Error('no capturable source available')
await invoke(screenCapture.setSource, { sourceId: source.id, options })
const stream = await navigator.mediaDevices.getUserMedia({ video: true })
Defensive patterns

Strategy: validation

Validate before calling

const sources = await invoke(screenCapture.getSources, options)
const target = sources.find(s => s.id === candidateId)
 ?? sources.find(s => s.name === candidateName) // fallback after window recreation
if (!target) {
  // refresh picker UI; do not call setSource
}
await invoke(screenCapture.setSource, { sourceId: target.id, options })

Try / catch

try {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true })
} catch (error) {
  // setDisplayMediaRequestHandler failures surface here: re-enumerate once and let the user re-pick
  const sources = await invoke(screenCapture.getSources, options)
  showSourcePicker(sources)
}

Prevention

When it happens

Trigger: Renderer picks a sourceId from an earlier getSources result, then the target window is closed, a display is unplugged/rearranged, or macOS screen-recording permission is revoked before setSource + getUserMedia run. Also: passing a sourceId enumerated with different options (e.g. different types or thumbnail sizes) than the ones in request.options, or passing a source id from a different Electron session/app restart (ids contain window/display handles and are not stable across restarts).

Common situations: A source-picker UI that lists windows once at startup and lets the user pick much later; long-running apps that cache a sourceId across display sleep/wake; multi-monitor rearrangement; passing 'window:123:0' style ids persisted from a previous app run.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/f1a107790320ffc7. Report an issue: GitHub.

Appendix: source

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

    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.`)
        }

        callback({
          video: source,
          audio: options?.loopbackWithMute ? LoopbackAudioTypes.LoopbackWithMute : LoopbackAudioTypes.Loopback,
        })
      })

      setSourceMutexTimeoutHandle = setTimeout(() => {
        if (screenCaptureSourceMutexHandle !== handle)
          return

        resetScreenCaptureSource()
        setSourceMutex.release()

        log
          .withFields({ windowId, windowTitle: tryWindowTitle(window, windowTitle) })
          .warn(

View on GitHub (pinned to 677329427f)