Exit 0 for all expected outcomes in quire-ci run_pipeline
Compile errors and empty pipelines are user-visible outcomes, not
quire-ci crashes. Both now emit RunFinished and return Ok(()) rather
than propagating errors that cause a non-zero exit. A non-zero exit
from quire-ci now exclusively means an unexpected failure (I/O error,
panic, dispatch file unreadable, etc.).
https://claude.ai/code/session_0135zw5K7qsn9thYkkpjX6HE
diff --git a/quire-ci/src/main.rs b/quire-ci/src/main.rs
index 58088fc..ff90cb6 100644
--- a/quire-ci/src/main.rs
+++ b/quire-ci/src/main.rs
@@ -425,17 +425,40 @@ fn load_dispatch(
fn run_pipeline(
workspace: PathBuf,
- sink: Box<dyn EventSink>,
+ mut sink: Box<dyn EventSink>,
log_dir: PathBuf,
git_dir: PathBuf,
meta: RunMeta,
secrets: HashMap<String, quire_core::secret::SecretString>,
_transport: TransportArgs,
) -> miette::Result<()> {
- let pipeline = compile_at(&workspace)?;
+ let pipeline = match compile_at(&workspace) {
+ Ok(p) => p,
+ Err(e) => {
+ tracing::warn!(
+ error = &*e as &(dyn std::error::Error + 'static),
+ "ci.fnl failed to compile"
+ );
+ sink.emit(Event {
+ at_ms: jiff::Timestamp::now().as_millisecond(),
+ kind: EventKind::RunFinished {
+ outcome: RunOutcome::PipelineFailure,
+ },
+ })
+ .expect("emit run_finished");
+ return Ok(());
+ }
+ };
let job_ids: Vec<String> = pipeline.jobs().iter().map(|j| j.id.clone()).collect();
if job_ids.is_empty() {
+ sink.emit(Event {
+ at_ms: jiff::Timestamp::now().as_millisecond(),
+ kind: EventKind::RunFinished {
+ outcome: RunOutcome::Success,
+ },
+ })
+ .expect("emit run_finished");
return Ok(());
}