Make scan_orphans propagate read_dir errors
Previously swallowed all read_dir errors silently. Now returns
Result<Vec<OpenedRun>> and only skips NotFound (directory simply
does not exist yet). reconcile_orphans and tests updated accordingly.

Assisted-by: GLM-5.1 via pi
change wyypmzsrsoszsrmypzpwlmlpvrusyvmw
commit 3179c5c98d0998a2ba15d4266b707d48068eeae8
author Alpha Chen <alpha@kejadlen.dev>
date
parent upopnlxv
diff --git a/src/ci.rs b/src/ci.rs
index 14ee1c9..9323870 100644
--- a/src/ci.rs
+++ b/src/ci.rs
@@ -102,16 +102,19 @@ impl Runs {
     /// The caller decides how to reconcile them:
     /// - `pending/` entries should be re-enqueued.
     /// - `active/` entries with no live runner should be marked failed.
-    pub fn scan_orphans(&self) -> Vec<OpenedRun> {
+    pub fn scan_orphans(&self) -> Result<Vec<OpenedRun>> {
         let mut orphans = Vec::new();
 
         for &state in &[RunState::Pending, RunState::Active] {
             let state_path = self.base.join(state.dir_name());
-            let Ok(entries) = fs_err::read_dir(&state_path) else {
-                continue;
+            let entries = match fs_err::read_dir(&state_path) {
+                Ok(entries) => entries,
+                Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
+                Err(e) => return Err(e.into()),
             };
 
-            for entry in entries.flatten() {
+            for entry in entries {
+                let entry = entry?;
                 let name = match entry.file_name().to_str() {
                     Some(n) => n.to_string(),
                     None => continue,
@@ -135,7 +138,7 @@ impl Runs {
             }
         }
 
-        orphans
+        Ok(orphans)
     }
 
     /// Reconcile orphaned runs from a previous server instance.
@@ -143,8 +146,8 @@ impl Runs {
     /// - `pending/` orphans are moved to `complete/` (will be re-enqueued when
     ///   the runner exists; for now, immediately completed).
     /// - `active/` orphans are moved to `failed/` (no live runner).
-    pub fn reconcile_orphans(&self) {
-        let orphans = self.scan_orphans();
+    pub fn reconcile_orphans(&self) -> Result<()> {
+        let orphans = self.scan_orphans()?;
         for orphan in &orphans {
             tracing::warn!(
                 run_id = %orphan.run.id(),
@@ -196,6 +199,8 @@ impl Runs {
                 _ => unreachable!("scan_orphans only returns pending/active"),
             }
         }
+
+        Ok(())
     }
 }
 
@@ -764,7 +769,7 @@ mod tests {
         let runs = Runs::new(quire.base_dir().join("runs").join("test.git"));
         let run = runs.create(&test_meta()).expect("create");
 
-        let orphans = runs.scan_orphans();
+        let orphans = runs.scan_orphans().expect("scan");
         assert_eq!(orphans.len(), 1);
         assert_eq!(orphans[0].run.id(), run.id());
         assert_eq!(orphans[0].run.state(), RunState::Pending);
@@ -777,7 +782,7 @@ mod tests {
         let mut run = runs.create(&test_meta()).expect("create");
         run.transition(RunState::Active).expect("transition");
 
-        let orphans = runs.scan_orphans();
+        let orphans = runs.scan_orphans().expect("scan");
         assert_eq!(orphans.len(), 1);
         assert_eq!(orphans[0].run.state(), RunState::Active);
     }
@@ -789,7 +794,7 @@ mod tests {
         let mut run = runs.create(&test_meta()).expect("create");
         run.transition(RunState::Complete).expect("transition");
 
-        let orphans = runs.scan_orphans();
+        let orphans = runs.scan_orphans().expect("scan");
         assert!(orphans.is_empty(), "complete runs are not orphans");
     }
 
@@ -797,7 +802,7 @@ mod tests {
     fn scan_orphans_empty_when_no_runs_dir() {
         let (_dir, quire) = tmp_quire();
         let runs = Runs::new(quire.base_dir().join("runs").join("test.git"));
-        assert!(runs.scan_orphans().is_empty());
+        assert!(runs.scan_orphans().expect("scan").is_empty());
     }
 
     #[test]
diff --git a/src/server.rs b/src/server.rs
index a2a0487..e14547a 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -39,7 +39,7 @@ pub async fn run(quire: &Quire) -> Result<()> {
 
     // Scan for orphaned runs from a previous server instance.
     for repo in quire.repos().context("failed to list repos")? {
-        repo.runs().reconcile_orphans();
+        repo.runs().reconcile_orphans()?;
     }
 
     let quire_handle = quire.clone();