fix(wfe-core): sub-workflow inherits parent workflow data

SubWorkflowStep was hard-coding `inputs: serde_json::Value::Null` from
the YAML compiler, so every `type: workflow` step kicked off a child
instance with an empty data object. Scripts in child workflows then
saw empty `$REPO_URL`, `$COMMIT_SHA`, etc. and failed immediately.

Now: when no explicit inputs are set, the child inherits the parent
workflow's data (when it's an object). Scripts in child workflows can
reference the same top-level inputs the parent was started with without
every `type: workflow` step needing to re-declare them.
This commit is contained in:
2026-04-07 18:38:41 +01:00
parent da26f142ee
commit 3915bcc1ec

View File

@@ -1,8 +1,8 @@
use async_trait::async_trait; use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use crate::models::schema::WorkflowSchema;
use crate::models::ExecutionResult; use crate::models::ExecutionResult;
use crate::models::schema::WorkflowSchema;
use crate::traits::step::{StepBody, StepExecutionContext}; use crate::traits::step::{StepBody, StepExecutionContext};
/// A step that starts a child workflow and waits for its completion. /// A step that starts a child workflow and waits for its completion.
@@ -110,12 +110,18 @@ impl StepBody for SubWorkflowStep {
) )
})?; })?;
// Use inputs if set, otherwise pass an empty object so the child // Use explicit inputs if set; otherwise inherit the parent workflow's
// workflow has a valid JSON object for storing step outputs. // data so child steps can reference the same top-level fields (e.g.
let child_data = if self.inputs.is_null() { // REPO_URL, COMMIT_SHA) without every `type: workflow` step having to
serde_json::json!({}) // re-declare them. Fall back to an empty object when the parent has
} else { // no data either so the child still has a valid JSON object for
// storing step outputs.
let child_data = if !self.inputs.is_null() {
self.inputs.clone() self.inputs.clone()
} else if context.workflow.data.is_object() {
context.workflow.data.clone()
} else {
serde_json::json!({})
}; };
let child_instance_id = host let child_instance_id = host
.start_workflow(&self.workflow_id, self.version, child_data) .start_workflow(&self.workflow_id, self.version, child_data)
@@ -132,8 +138,8 @@ impl StepBody for SubWorkflowStep {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::models::schema::SchemaType;
use crate::models::ExecutionPointer; use crate::models::ExecutionPointer;
use crate::models::schema::SchemaType;
use crate::primitives::test_helpers::*; use crate::primitives::test_helpers::*;
use crate::traits::step::HostContext; use crate::traits::step::HostContext;
use serde_json::json; use serde_json::json;
@@ -170,10 +176,7 @@ mod tests {
let def_id = definition_id.to_string(); let def_id = definition_id.to_string();
let result_id = self.result_id.clone(); let result_id = self.result_id.clone();
Box::pin(async move { Box::pin(async move {
self.started self.started.lock().unwrap().push((def_id, version, data));
.lock()
.unwrap()
.push((def_id, version, data));
Ok(result_id) Ok(result_id)
}) })
} }
@@ -265,10 +268,7 @@ mod tests {
let result = step.run(&ctx).await.unwrap(); let result = step.run(&ctx).await.unwrap();
assert!(result.proceed); assert!(result.proceed);
assert_eq!( assert_eq!(result.output_data, Some(json!({"result": "success"})));
result.output_data,
Some(json!({"result": "success"}))
);
} }
#[tokio::test] #[tokio::test]
@@ -292,10 +292,7 @@ mod tests {
let result = step.run(&ctx).await.unwrap(); let result = step.run(&ctx).await.unwrap();
assert!(result.proceed); assert!(result.proceed);
assert_eq!( assert_eq!(result.output_data, Some(json!({"a": 1, "b": 2})));
result.output_data,
Some(json!({"a": 1, "b": 2}))
);
} }
#[tokio::test] #[tokio::test]