{"record":{"id":"52dec31d13458690","repo":"moeru-ai/airi","slug":"no-screen-source-available","errorCode":null,"errorMessage":"No screen source available","messagePattern":"No screen source available","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-shared/src/beat-sync/detector.ts","lineNumber":170,"sourceCode":"        stopSource = () => {\n          stream.getTracks().forEach(track => track.stop())\n        }\n\n        return node\n      }\n      case StageEnvironment.Tamagotchi: {\n        if (!isElectronWindow(window)) {\n          throw new Error(`Electron window is required for this environment: ${options.env}`)\n        }\n\n        // FIXME(Makito): Will refactor later\n        const { createContext } = await import('@moeru/eventa/adapters/electron/renderer')\n        const { selectWithSource } = setupElectronScreenCapture(createContext(window.electron.ipcRenderer).context)\n\n        const stream = await selectWithSource(\n          (sources: SerializableDesktopCapturerSource[]) => {\n            if (sources.length === 0)\n              throw new Error('No screen source available')\n            return sources[0].id\n          },\n          async () => await navigator.mediaDevices.getDisplayMedia({\n            video: true,\n            audio: true,\n          }),\n          { sourcesOptions: { types: ['screen'] } },\n        )\n\n        const videoTracks = stream.getVideoTracks()\n\n        videoTracks.forEach((track: MediaStreamTrack) => {\n          track.stop()\n          stream.removeTrack(track)\n        })\n\n        const node = ctx.createMediaStreamSource(stream)\n        stopSource = () => {","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-shared/src/beat-sync/detector.ts#L152-L188","documentation":"Thrown by the beat-sync detector's Tamagotchi (Electron) path when `desktopCapturer` returns an empty source list, i.e. no screen sources were available to select. The selector callback receives `sources` and throws if its length is 0 before returning a source id, so the capture cannot proceed.","triggerScenarios":"Running beat sync in the Electron (stage-tamagotchi) environment where `desktopCapturer.fetchSources({ types: ['screen'] })` yields no sources. This can happen when screen-recording permissions are denied, when running in a headless/sandboxed context with no displays, or when the OS blocks enumeration.","commonSituations":"macOS Screen Recording permission not granted to the Electron app; running in CI/headless without a virtual display; kiosk/lockdown policies that block screen enumeration; display sleep or no attached monitors.","solutions":["Grant Screen Recording permission to the Electron app in macOS System Settings (Privacy & Security), then restart the app.","In headless/CI environments, provide a virtual display (xvfb) or skip beat-sync entirely.","Catch the error and surface a user-facing message guiding the user to enable screen-capture permission.","Confirm `types: ['screen']` matches available sources; broaden to include 'window' if appropriate for the use case."],"exampleFix":"// before\nconst stream = await selectWithSource(sources => {\n  if (sources.length === 0) throw new Error('No screen source available')\n  return sources[0].id\n}, ...)\n\n// after\ntry {\n  await startBeatSync()\n} catch (e) {\n  if (e.message === 'No screen source available') {\n    showUser('Grant screen-recording permission to this app, then try again.')\n  } else throw e\n}","handlingStrategy":"try-catch","validationCode":"import { desktopCapturer } from 'electron'\n\nasync function hasScreenSource(): Promise<boolean> {\n  const sources = await desktopCapturer.getSources({ types: ['screen'] })\n  return sources.length > 0\n}\n\nif (!await hasScreenSource()) {\n  throw new Error('No screen source available. Grant screen-recording permission.')\n}","typeGuard":"function hasScreenSources(sources: unknown[]): boolean {\n  return Array.isArray(sources) && sources.length > 0\n}","tryCatchPattern":"try {\n  await startBeatSync()\n} catch (e) {\n  if (e instanceof Error && e.message === 'No screen source available') {\n    showUser('Grant screen-recording permission to this app in System Settings, then restart.')\n    return\n  }\n  throw e\n}","preventionTips":["On macOS, grant Screen Recording permission to the Electron app and restart it.","In headless/CI, provide a virtual display (xvfb) or skip beat sync.","Catch the error and guide the user to the relevant OS privacy setting.","Confirm a display is attached and not sleeping before invoking capture."],"tags":["beat-sync","electron","desktop-capturer","permissions","media-capture"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}