Add container-lifecycle error variants
Introduce typed variants for the per-run container lifecycle
(WorkspaceMaterializationFailed, ImageBuildFailed,
ContainerStartFailed) and route the materialize_workspace
non-success exit through the dedicated variant. The other two
variants land now even without producers so the error surface
change is atomic.

Assisted-by: Claude Opus 4.7 via Claude Code
change nuumnvumrzouvtnowxponwlkuyqxoolk
commit 384bab689e860d061f548a0b50b487f4dddcaab2
author Alpha Chen <alpha@kejadlen.dev>
date
parent ztowqmkl
diff --git a/src/ci/run.rs b/src/ci/run.rs
index 5834e35..b10bbe4 100644
--- a/src/ci/run.rs
+++ b/src/ci/run.rs
@@ -466,11 +466,11 @@ pub fn materialize_workspace(
     let tar_status = tar.wait()?;
     let archive_status = archive.wait()?;
     if !archive_status.success() || !tar_status.success() {
-        // Task 3 introduces Error::WorkspaceMaterializationFailed; for
-        // now use std::io::Error wrapped as Error::Io. Task 3 swaps it.
-        return Err(Error::Io(std::io::Error::other(format!(
-            "materialize_workspace: git archive exited {archive_status}, tar exited {tar_status}"
-        ))));
+        return Err(Error::WorkspaceMaterializationFailed {
+            source: std::io::Error::other(format!(
+                "git archive exited {archive_status}, tar exited {tar_status}"
+            )),
+        });
     }
     Ok(())
 }
@@ -590,6 +590,41 @@ mod tests {
         );
     }
 
+    #[test]
+    fn materialize_workspace_errors_on_unknown_sha() {
+        let dir = tempfile::tempdir().expect("tempdir");
+        let src_repo = dir.path().join("src");
+        fs_err::create_dir_all(&src_repo).expect("mkdir src");
+
+        let env_vars: [(&str, &str); 6] = [
+            ("GIT_AUTHOR_NAME", "test"),
+            ("GIT_AUTHOR_EMAIL", "test@test"),
+            ("GIT_COMMITTER_NAME", "test"),
+            ("GIT_COMMITTER_EMAIL", "test@test"),
+            ("GIT_CONFIG_GLOBAL", "/dev/null"),
+            ("GIT_CONFIG_SYSTEM", "/dev/null"),
+        ];
+        let out = std::process::Command::new("git")
+            .args(["init", "-b", "main"])
+            .current_dir(&src_repo)
+            .envs(env_vars)
+            .output()
+            .expect("git init");
+        assert!(out.status.success());
+
+        let workspace = dir.path().join("ws");
+        let err = materialize_workspace(
+            &src_repo.join(".git"),
+            "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
+            &workspace,
+        )
+        .expect_err("expected failure on unknown SHA");
+        assert!(
+            matches!(err, Error::WorkspaceMaterializationFailed { .. }),
+            "expected WorkspaceMaterializationFailed, got: {err:?}"
+        );
+    }
+
     #[test]
     fn run_state_dir_name() {
         assert_eq!(RunState::Pending.dir_name(), "pending");
diff --git a/src/error.rs b/src/error.rs
index 5533a63..80478b1 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -40,6 +40,24 @@ pub enum Error {
         source: Box<Error>,
     },
 
+    #[error("workspace materialization failed")]
+    WorkspaceMaterializationFailed {
+        #[source]
+        source: std::io::Error,
+    },
+
+    #[error("image build failed")]
+    ImageBuildFailed {
+        #[source]
+        source: std::io::Error,
+    },
+
+    #[error("container start failed")]
+    ContainerStartFailed {
+        #[source]
+        source: std::io::Error,
+    },
+
     #[error("git error: {0}")]
     Git(String),