Skip to content

Conditional branching broken on Argo Workflows v3.7.11+ / v4.0.2+ #3043

Description

@cboylston

Description

Flows using conditional branching (self.next({...}, condition=...)) hang indefinitely on Argo Workflows v3.7.11+ and v4.0.2+. The convergence step after conditional branches is never scheduled.

Reproducer

The metaflow-music-agent example reproduces this. The ResearchAgentFlow uses:

self.next(
    {
        "continue": self.research,
        "done": self.done,
        "unknown": self.unknown,
        "failed": self.failed,
    },
    condition="decision",
)

done, unknown, and failed all converge on agent_complete -> join -> end. Only one branch executes per foreach iteration; the others are skipped.

Run with:

python agentflow.py --environment=pypi --with kubernetes run --max-artists 2

The research steps complete successfully, each branch reaches done or unknown, but agent_complete is never scheduled and the workflow hangs in Running state.

Root Cause

Two interacting issues:

1. How Metaflow generates input-paths for conditional join steps

For a convergence step like agent_complete, the generated Argo DAG template references outputs from all conditional branches unconditionally:

arguments:
  parameters:
    - name: input-paths
      value: "argo-{{workflow.name}}/:done/{{tasks.done.outputs.parameters.task-id}},failed/{{tasks.failed.outputs.parameters.task-id}},unknown/{{tasks.unknown.outputs.parameters.task-id}}"
depends: "done.Succeeded || failed.Succeeded || unknown.Succeeded"

The depends clause is correct (OR), but input-paths references task-id from all three branches. Skipped branches produce no outputs.

Metaflow's intended workaround (introduced in #2560) is to base64-encode the value and filter out unresolved paths at runtime via conditional_input_paths.py. This depends on Argo passing through unresolved {{tasks.X...}} expressions as literal strings.

2. Argo Workflows changed parameter resolution behavior

Argo PR argoproj/argo-workflows#15442 ("fix: requeue workflow if expected variables are missing", Feb 26 2026) changed dag.go from:

// Before (v3.7.10, v4.0.1 and earlier) — unresolved expressions passed through
newTaskStr, err := template.Replace(ctx, string(taskBytes), mergedParams, true)

to:

// After (v3.7.11, v4.0.2+) — unresolved tasks.*/steps.* references cause requeue
newTaskStr, err := template.ReplaceStrict(ctx, string(taskBytes), mergedParams, []string{"tasks", "steps"})
if err != nil {
    if template.IsMissingVariableErr(err) {
        woc.requeue()
        return nil, ErrRequeue  // pod never created, runtime filter never runs
    }
}

The controller now requeues indefinitely when it can't resolve {{tasks.failed.outputs.parameters.task-id}} for a skipped task. The conditional_input_paths.py runtime filter never gets a chance to execute.

Controller logs confirm this

level=WARN msg="was unable to find variable" error="failed to resolve {{tasks.failed.outputs.parameters.task-id}}"
level=WARN msg="was unable to find variable" error="failed to resolve {{tasks.done.outputs.parameters.task-id}}"
level=WARN msg="was unable to obtain the node" taskName=agent-complete

Affected Versions

Argo Version Behavior Conditionals work?
v3.5.x - v3.7.10 Replace(allowUnresolved=true) Yes
v3.7.11+ ReplaceStrict No
v4.0.0 - v4.0.1 Replace(allowUnresolved=true) Yes
v4.0.2+ ReplaceStrict No

Tested with Metaflow 2.19.22. Likely affects all versions since #2560 (Aug 2025).

History

For context, the original conditionals PR (#2550, Aug 19 2025) used safe Argo expressions that handled skipped tasks correctly:

{{=(get(tasks['done']?.outputs?.parameters, 'task-id') ?? 'no-task')}}

This was removed two days later in #2560 (Aug 21 2025) because ?. optional chaining doesn't work inside foreach (withParam nested DAGs evaluate all ?. as false). The replacement strategy relied on Argo's passthrough behavior, which is no longer guaranteed.

Argo-side tracking

This is related to argoproj/argo-workflows#15737. A fix (PR #15736) has been merged and cherry-picked to v4.0 and v3.7, but that fix targets expanded task properties (withItems/withParams). It may not fully cover the skipped conditional branch case — worth verifying once v3.7.12 / v4.0.4 are released.

Workaround

Downgrade Argo Workflows to v3.7.10 or v4.0.1.

Environment

  • Metaflow: 2.19.22
  • Argo Workflows: tested on v4.0.3 and v3.7.11
  • Kubernetes: EKS
  • Python: 3.12

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions