moeru-ai/airi · error · Error

No active source selected

Error message

No active source selected

What it means

Thrown by use-vision-screen-capture.startStream() when activeSourceId.value is empty. The composable requires a selected screen source before it can request a display-media stream; the guard fires before any getDisplayMedia call.

Source

Thrown at apps/stage-tamagotchi/src/renderer/composables/use-vision-screen-capture.ts:123

        ...source,
        appIconURL: source.appIcon && source.appIcon.length > 0 ? createObjectUrlFromBytes(source.appIcon, 'image/png') : undefined,
        thumbnailURL: source.thumbnail && source.thumbnail.length > 0 ? createObjectUrlFromBytes(source.thumbnail, 'image/jpeg') : undefined,
      }))

      const hasActiveSource = sources.value.some(source => source.id === activeSourceId.value)
      const nextActiveSourceId = hasActiveSource ? activeSourceId.value : sources.value[0]?.id || ''
      activeSourceId.value = nextActiveSourceId
    }
    finally {
      isRefetching.value = false
      hasFetchedOnce.value = true
    }
  }

  async function startStream() {
    const sourceId = activeSourceId.value
    if (!sourceId)
      throw new Error('No active source selected')

    if (isActiveStream(activeStream.value) && activeStreamSourceId.value === sourceId)
      return activeStream.value!

    clearActiveStream()

    const stream = await selectWithSource(
      () => sourceId,
      async () => await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false }),
    )
    if (!isActiveStream(stream)) {
      stream.getTracks().forEach(track => track.stop())
      throw new Error('Selected source did not provide a live video track')
    }

    activeStream.value = stream
    activeStreamSourceId.value = sourceId
    attachStreamLifecycle(stream, sourceId)

View on GitHub (pinned to 27111382b4)

Solutions

  1. Ensure fetchSources() has completed and sources.value is non-empty before enabling the start button.
  2. Bind the source picker to activeSourceId and require a selection (or auto-select sources[0]) before startStream.
  3. Gate startStream behind `if (!activeSourceId.value) return` or show a 'select a source' prompt.
  4. Check that screen-capture permission was granted in the OS/browser.

Example fix

// before
async function startStream() {
  const sourceId = activeSourceId.value
  if (!sourceId) throw new Error('No active source selected')
}

// after
async function startStream() {
  if (!activeSourceId.value && sources.value[0])
    activeSourceId.value = sources.value[0].id
  const sourceId = activeSourceId.value
  if (!sourceId) throw new Error('No active source selected')
}
Defensive patterns

Strategy: validation

Validate before calling

function canStartStream(): boolean {
  return !!activeSourceId.value && sources.value.some(s => s.id === activeSourceId.value)
}

Type guard

function hasActiveSource(id: string | undefined, sources: { id: string }[]): id is string {
  return typeof id === 'string' && id.length > 0 && sources.some(s => s.id === id)
}

Prevention

When it happens

Trigger: startStream() called before fetchSources() resolved, or after sources came back empty, or when the UI never bound a selection to activeSourceId. Also if the user's permission prompt during enumeration returned zero sources.

Common situations: Race: component calls startStream on mount before the async source enumeration completes; user revoked screen-capture permission so sources list is empty; refetch failed silently leaving activeSourceId unset.

Related errors


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