{"record":{"id":"74938f0b5841a5a1","repo":"screenpipe/screenpipe","slug":"monitor-not-found","errorCode":null,"errorMessage":"Monitor {} not found","messagePattern":"Monitor (.+?) not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/screenpipe-engine/src/vision_manager/manager.rs","lineNumber":534,"sourceCode":"    }\n\n    /// Start recording on a specific monitor\n    pub async fn start_monitor(&self, monitor_id: u32) -> Result<()> {\n        // Preserve the cheap intent/idempotency guards before any OS lookup.\n        if self.user_disabled.contains(&monitor_id) {\n            debug!(\"Monitor {} is user-paused; skipping start\", monitor_id);\n            return Ok(());\n        }\n        if self.recording_tasks.contains_key(&monitor_id) {\n            debug!(\"Monitor {} is already recording\", monitor_id);\n            return Ok(());\n        }\n        // Public id-only callers (resume APIs and external control surfaces)\n        // still need a lookup. On macOS this path is bounded by the shared SCK\n        // enumeration admission budget in screenpipe-screen.\n        let monitor = get_monitor_by_id(monitor_id)\n            .await\n            .ok_or_else(|| anyhow::anyhow!(\"Monitor {} not found\", monitor_id))?;\n        self.start_monitor_handle(monitor).await\n    }\n\n    /// Start from a monitor handle that was already returned by a bounded\n    /// enumeration. Startup, watchdog recovery, and hot-plug reconciliation\n    /// all have this handle in hand; re-enumerating by id here used to create\n    /// an unbounded second `SCShareableContent` callback during recovery.\n    pub(crate) async fn start_monitor_handle(\n        &self,\n        monitor: screenpipe_screen::monitor::SafeMonitor,\n    ) -> Result<()> {\n        let monitor_id = monitor.id();\n        // Record selection intent before the user-pause guard. A paused display\n        // remains expected even though it intentionally has no active task.\n        self.expected_monitors\n            .write()\n            .unwrap_or_else(|e| e.into_inner())\n            .insert(monitor_id);","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/screenpipe/screenpipe/blob/4ebf712990fee17eeaf904dacf749b6e96ac9bf3/crates/screenpipe-engine/src/vision_manager/manager.rs#L516-L552","documentation":"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.","triggerScenarios":"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.","commonSituations":"Resuming a paused monitor after the user changed display setup; automations holding old ids across reboots; UI showing cached monitor list.","solutions":["Re-enumerate monitors via get_monitor_by_id/available list and use a fresh handle before starting","Fall back to starting the primary or first available monitor when the saved id is missing","Clear the stale monitor id from persisted settings when this error occurs","Subscribe to hot-plug/display-change events to keep stored ids current"],"exampleFix":"// before\nmanager.start_monitor(saved_monitor_id).await?; // fails if id gone\n// after\nmatch manager.start_monitor(saved_monitor_id).await {\n    Ok(_) => (),\n    Err(e) if e.to_string().contains(\"not found\") => {\n        if let Some(m) = first_available_monitor().await {\n            manager.start_monitor_handle(m).await?;\n        }\n    }\n    Err(e) => return Err(e.into()),\n}","handlingStrategy":"validation","validationCode":"// Confirm the monitor exists before resuming by id\nif get_monitor_by_id(monitor_id).await.is_none() {\n    // re-enumerate or fall back to first available monitor\n    return start_first_available_monitor().await;\n}\nmanager.start_monitor(monitor_id).await?;","typeGuard":null,"tryCatchPattern":"match manager.start_monitor(monitor_id).await {\n    Err(e) if e.to_string().contains(\"not found\") => {\n        warn!(\"monitor {monitor_id} gone; selecting current monitor\");\n        let m = first_available_monitor().await.ok_or(anyhow!(\"no monitors\"))?;\n        manager.start_monitor_handle(m).await\n    }\n    r => r,\n}","preventionTips":["Prefer start_monitor_handle with a freshly enumerated handle over long-lived ids","Invalidate stored monitor ids on hot-plug, sleep/wake, and reboot events","Always offer a fallback to the primary/first monitor in resume flows","Never persist monitor ids across machines or config restores"],"tags":["monitor","screen-capture","stale-id","rust"],"backgroundTag":"monitor-not-found","analyzedSha":"4ebf712990fee17eeaf904dacf749b6e96ac9bf3","analyzedAt":"2026-09-01T23:33:43.065Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}