screenpipe/screenpipe · error

Monitor {} not found

Error message

Monitor {} not found

What it means

start_monitor with only a monitor id performs a get_monitor_by_id lookup; when no monitor with that id is currently enumerated it fails with 'Monitor {id} not found'. This is the id-only path used by resume APIs and external control surfaces, and it fails whenever the referenced display is gone or its id changed.

Source

Thrown at crates/screenpipe-engine/src/vision_manager/manager.rs:534

    }

    /// Start recording on a specific monitor
    pub async fn start_monitor(&self, monitor_id: u32) -> Result<()> {
        // Preserve the cheap intent/idempotency guards before any OS lookup.
        if self.user_disabled.contains(&monitor_id) {
            debug!("Monitor {} is user-paused; skipping start", monitor_id);
            return Ok(());
        }
        if self.recording_tasks.contains_key(&monitor_id) {
            debug!("Monitor {} is already recording", monitor_id);
            return Ok(());
        }
        // Public id-only callers (resume APIs and external control surfaces)
        // still need a lookup. On macOS this path is bounded by the shared SCK
        // enumeration admission budget in screenpipe-screen.
        let monitor = get_monitor_by_id(monitor_id)
            .await
            .ok_or_else(|| anyhow::anyhow!("Monitor {} not found", monitor_id))?;
        self.start_monitor_handle(monitor).await
    }

    /// Start from a monitor handle that was already returned by a bounded
    /// enumeration. Startup, watchdog recovery, and hot-plug reconciliation
    /// all have this handle in hand; re-enumerating by id here used to create
    /// an unbounded second `SCShareableContent` callback during recovery.
    pub(crate) async fn start_monitor_handle(
        &self,
        monitor: screenpipe_screen::monitor::SafeMonitor,
    ) -> Result<()> {
        let monitor_id = monitor.id();
        // Record selection intent before the user-pause guard. A paused display
        // remains expected even though it intentionally has no active task.
        self.expected_monitors
            .write()
            .unwrap_or_else(|e| e.into_inner())
            .insert(monitor_id);

View on GitHub (pinned to 4ebf712990)

Solutions

  1. Re-enumerate monitors via get_monitor_by_id/available list and use a fresh handle before starting
  2. Fall back to starting the primary or first available monitor when the saved id is missing
  3. Clear the stale monitor id from persisted settings when this error occurs
  4. Subscribe to hot-plug/display-change events to keep stored ids current

Example fix

// before
manager.start_monitor(saved_monitor_id).await?; // fails if id gone
// after
match manager.start_monitor(saved_monitor_id).await {
    Ok(_) => (),
    Err(e) if e.to_string().contains("not found") => {
        if let Some(m) = first_available_monitor().await {
            manager.start_monitor_handle(m).await?;
        }
    }
    Err(e) => return Err(e.into()),
}
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the monitor exists before resuming by id
if get_monitor_by_id(monitor_id).await.is_none() {
    // re-enumerate or fall back to first available monitor
    return start_first_available_monitor().await;
}
manager.start_monitor(monitor_id).await?;

Try / catch

match manager.start_monitor(monitor_id).await {
    Err(e) if e.to_string().contains("not found") => {
        warn!("monitor {monitor_id} gone; selecting current monitor");
        let m = first_available_monitor().await.ok_or(anyhow!("no monitors"))?;
        manager.start_monitor_handle(m).await
    }
    r => r,
}

Prevention

When it happens

Trigger: resume_monitor (or any external control API) called with a monitor_id that is no longer present — monitor unplugged, id re-assigned after replug/sleep, or the id came from a stale persisted config or another machine.

Common situations: Resuming a paused monitor after the user changed display setup; automations holding old ids across reboots; UI showing cached monitor list.

Related errors


AI-assisted analysis of screenpipe/screenpipe@4ebf712990 (2026-09-01). Data as JSON: /api/errors/74938f0b5841a5a1. Report an issue: GitHub.