Skip to content

Commit c474304

Browse files
intel352claude
andcommitted
fix: if/skip_if guards no longer mark skipped steps as executed (closes #362)
Add Skipped bool to StepResult. When a SkippableStep bypasses execution, the pipeline executor detects result.Skipped and skips MergeStepOutput, so StepExecuted("name") correctly returns false for guard-skipped steps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 39d075e commit c474304

4 files changed

Lines changed: 91 additions & 0 deletions

File tree

interfaces/pipeline.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ type StepResult struct {
108108

109109
// Stop indicates the pipeline should stop after this step (success).
110110
Stop bool
111+
112+
// Skipped indicates the step was bypassed by a guard (skip_if or if).
113+
// When true, the pipeline executor will not record this step in StepOutputs.
114+
Skipped bool
111115
}
112116

113117
// PipelineStep is a single composable unit of work in a pipeline.

module/pipeline_executor.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,18 @@ func (p *Pipeline) Execute(ctx context.Context, triggerData map[string]any) (*Pi
224224
}
225225
}
226226

227+
// Guard-skipped steps are not recorded in StepOutputs.
228+
if result != nil && result.Skipped {
229+
logger.Info("Step skipped by guard", "pipeline", p.Name, "step", step.Name(), "elapsed", elapsed)
230+
p.recordEvent(ctx, "step.skipped", map[string]any{
231+
"step_name": step.Name(),
232+
"reason": result.Output["reason"],
233+
"elapsed": elapsed.String(),
234+
})
235+
i++
236+
continue
237+
}
238+
227239
logger.Info("Step completed", "pipeline", p.Name, "step", step.Name(), "elapsed", elapsed)
228240

229241
// Record step.completed

module/pipeline_step_skip.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func isTruthy(val string) bool {
8787
// skippedResult builds the standard output for a step that was skipped by a guard.
8888
func skippedResult(reason string) *interfaces.StepResult {
8989
return &interfaces.StepResult{
90+
Skipped: true,
9091
Output: map[string]any{
9192
"skipped": true,
9293
"reason": reason,

wftest/harness_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,77 @@ pipelines:
204204
t.Error("expected error for missing pipeline")
205205
}
206206
}
207+
208+
func TestSkipIf_StepNotTrackedAsExecuted(t *testing.T) {
209+
h := wftest.New(t, wftest.WithYAML(`
210+
pipelines:
211+
test:
212+
steps:
213+
- name: always_runs
214+
type: step.set
215+
config:
216+
values:
217+
flag: "true"
218+
- name: skipped_step
219+
type: step.set
220+
skip_if: '{{ eq .flag "true" }}'
221+
config:
222+
values:
223+
should_not_run: true
224+
- name: final
225+
type: step.set
226+
config:
227+
values:
228+
done: true
229+
`))
230+
result := h.ExecutePipeline("test", nil)
231+
if result.Error != nil {
232+
t.Fatal(result.Error)
233+
}
234+
if !result.StepExecuted("always_runs") {
235+
t.Error("always_runs should have been executed")
236+
}
237+
if result.StepExecuted("skipped_step") {
238+
t.Error("skipped_step should NOT have been executed (skip_if was true)")
239+
}
240+
if !result.StepExecuted("final") {
241+
t.Error("final should have been executed")
242+
}
243+
}
244+
245+
func TestIf_StepNotTrackedWhenConditionFalse(t *testing.T) {
246+
h := wftest.New(t, wftest.WithYAML(`
247+
pipelines:
248+
test:
249+
steps:
250+
- name: set_flag
251+
type: step.set
252+
config:
253+
values:
254+
flag: "true"
255+
- name: conditional_step
256+
type: step.set
257+
if: '{{ eq .flag "false" }}'
258+
config:
259+
values:
260+
should_not_run: true
261+
- name: final
262+
type: step.set
263+
config:
264+
values:
265+
done: true
266+
`))
267+
result := h.ExecutePipeline("test", nil)
268+
if result.Error != nil {
269+
t.Fatal(result.Error)
270+
}
271+
if !result.StepExecuted("set_flag") {
272+
t.Error("set_flag should have been executed")
273+
}
274+
if result.StepExecuted("conditional_step") {
275+
t.Error("conditional_step should NOT have been executed (if condition was false)")
276+
}
277+
if !result.StepExecuted("final") {
278+
t.Error("final should have been executed")
279+
}
280+
}

0 commit comments

Comments
 (0)