Skip to content

Fix NextFlow continuing processes when some inputs fail Step-by-Step Guide

In Nextflow, a pipeline represents a Directed Acyclic Graph (DAG). By default, if a process fails, Nextflow attempts to gracefully shut down by allowing currently running tasks to finish but preventing new ones from starting.

The issue of “continuing processes when inputs fail” typically manifests in two ways:

  1. Zombie Executions: Downstream processes trigger even though an upstream process failed or produced invalid/empty data.
  2. Graceful Completion vs. Immediate Termination: The pipeline stays active because the errorStrategy is set to ignore or finish, passing “null” or empty emissions down the line.

When processes continue with missing inputs, it usually indicates that the channel logic is not validating the integrity of the data passed between tasks, or the error handling scope is too broad.

Cause Description
Permissive Error Strategy Using errorStrategy 'ignore' allows the pipeline to proceed even if a task returns a non-zero exit code.
Empty Channel Propagation A process fails to produce a file, but the channel still emits a signal, causing downstream tasks to run with missing files.
Implicit Exit 0 Shell scripts within the script: block might catch errors internally but exit with 0, misleading Nextflow.
Optional Input Logic Using optional: true in output declarations allows a process to finish successfully without creating the output file.
Complex Joins combine or join operators waiting for multiple inputs might trigger partially if the channel logic is flawed.

The quickest way to stop the “leakage” of failed processes is to set a global or process-specific terminate strategy.

// nextflow.config or process definition
process {
// This ensures the pipeline stops immediately on any error
errorStrategy = 'terminate'
}

If you are using errorStrategy 'ignore', you must ensure that downstream processes do not receive empty or null values. Use the filter operator to sanitize channels.

workflow {
data_ch = PROCESS_A(input_ch)
// Ensure only valid, non-empty emissions reach PROCESS_B
filtered_ch = data_ch.filter { it -> it.size() > 0 && it[1] != null }
PROCESS_B(filtered_ch)
}

3. Implement checkIfExists for File Channels

Section titled “3. Implement checkIfExists for File Channels”

When creating channels from paths, ensure the files actually exist before the DAG starts execution.

// Use checkIfExists to fail early during the initialization phase
input_ch = Channel
.fromPath(params.input_files, checkIfExists: true)
.map { file -> [file.baseName, file] }

If your processes are continuing because the shell script is hiding failures, force the shell to be “noisy” by using set -e and set -o pipefail.

Terminal window
process ANALYSIS {
shell:
'''
# Exit immediately if a command fails
set -e
set -o pipefail
run_tool --input !{input} > output.txt
'''
}

To identify why a process is triggering with bad inputs, use the dump() operator to inspect the channel contents in the console output.

workflow {
Channel.from('A', 'B', 'C')
| map { it == 'B' ? null : it }
| dump(tag: 'debug_channel')
| PROCESS_X
}
  1. Avoid errorStrategy 'ignore' Global Policy: Only apply “ignore” to non-critical tasks. Use errorStrategy { task.exitStatus in 1..2 ? 'retry' : 'terminate' } for more granular control.
  2. Explicit Output Matching: In the output: block, avoid using generic patterns if possible. If a file is mandatory, do not use optional: true.
  3. Use DSL2 Modules: Isolate logic into modules. This makes it easier to test individual components and their failure modes.
  4. Clean up Work Directory: Use nextflow clean -f frequently during development. Sometimes “continuing” processes are actually using cached results from a previous successful (but different) run.
  5. Assert Inputs: Use Groovy assert statements within the script: or exec: block to validate metadata before the main shell command runs.