From 7315432cda65e3e05c17e85d0a75b33a528180cb Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Sat, 25 Apr 2026 21:24:37 +0100 Subject: [PATCH 1/3] fix(trimgalore): clean stale outputs on same-workdir retry trim_galore overwrites its outputs on re-run but never deletes orphans from a prior interrupted attempt. When AWS Batch retries a job in the same workdir after a Spot reclaim, an intermediate `*_trimmed.fq.gz` written by the failed attempt can survive into the successful retry, get matched by the `reads` output glob, and break downstream consumers that expect 1-2 fastq inputs (e.g. fq/lint). Reported via nf-core/rnaseq users running 3.23.0+ on Spot+Fusion. See also nf-core/rnaseq#1807. Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/nf-core/trimgalore/main.nf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/nf-core/trimgalore/main.nf b/modules/nf-core/trimgalore/main.nf index 0553bb54c4c8..d019d077e43a 100644 --- a/modules/nf-core/trimgalore/main.nf +++ b/modules/nf-core/trimgalore/main.nf @@ -46,6 +46,9 @@ process TRIMGALORE { def args_list = args.split("\\s(?=--)").toList() args_list.removeAll { arg -> arg.toLowerCase().contains('_r2 ') } """ + # Remove any trim_galore outputs left behind by a previous attempt that + # ran in this same workdir (e.g. AWS Batch retry after a Spot reclaim). + rm -f *.fq.gz *.html *.zip *_trimming_report.txt [ ! -f ${prefix}.fastq.gz ] && ln -s ${reads} ${prefix}.fastq.gz trim_galore \\ ${args_list.join(' ')} \\ @@ -56,6 +59,9 @@ process TRIMGALORE { } else { """ + # Remove any trim_galore outputs left behind by a previous attempt that + # ran in this same workdir (e.g. AWS Batch retry after a Spot reclaim). + rm -f *.fq.gz *.html *.zip *_trimming_report.txt [ ! -f ${prefix}_1.fastq.gz ] && ln -s ${reads[0]} ${prefix}_1.fastq.gz [ ! -f ${prefix}_2.fastq.gz ] && ln -s ${reads[1]} ${prefix}_2.fastq.gz trim_galore \\ From d9fc618cdfd5c01abc19ca4a7abfb57e997be34c Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Sat, 25 Apr 2026 23:21:02 +0100 Subject: [PATCH 2/3] fix(trimgalore): pin cutadapt and python in env.yml to match docker The docker container `quay.io/biocontainers/trim-galore:0.6.10--hdfd78af_2` ships with cutadapt 5.2 and Python 3.12.12. Without explicit pins, the conda solver picks newer versions (currently Python 3.13.13), which desyncs from the docker container and breaks the snapshot test that captures the trim_galore log line `This is cutadapt 5.2 with Python X.Y.Z`. Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/nf-core/trimgalore/environment.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/nf-core/trimgalore/environment.yml b/modules/nf-core/trimgalore/environment.yml index 60b33ef21074..5321b145cd38 100644 --- a/modules/nf-core/trimgalore/environment.yml +++ b/modules/nf-core/trimgalore/environment.yml @@ -5,3 +5,5 @@ channels: - bioconda dependencies: - bioconda::trim-galore=0.6.10 + - bioconda::cutadapt=5.2 + - conda-forge::python=3.12.12 From 5bb02b721a11fd24cc6eb0f3e91a1512fb423b0c Mon Sep 17 00:00:00 2001 From: Jonathan Manning Date: Sat, 25 Apr 2026 23:24:21 +0100 Subject: [PATCH 3/3] test(trimgalore): drop python pin, filter python version from snapshot The cutadapt log line "This is cutadapt 5.2 with Python X.Y.Z" includes the runtime Python version, which is a function of whatever conda resolves at solve time and not something the module is responsible for. Pinning Python in environment.yml just to satisfy the snapshot is brittle - every patch release would need an env bump. Filter that line out of the snapshotted log chunks instead. Cutadapt version is still asserted via the separate "Cutadapt version: 5.2" header line, and cutadapt itself remains pinned in environment.yml because it actually drives trimming behavior. Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/nf-core/trimgalore/environment.yml | 1 - modules/nf-core/trimgalore/tests/main.nf.test | 10 +++++----- modules/nf-core/trimgalore/tests/main.nf.test.snap | 5 ----- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/modules/nf-core/trimgalore/environment.yml b/modules/nf-core/trimgalore/environment.yml index 5321b145cd38..b2b79fe33692 100644 --- a/modules/nf-core/trimgalore/environment.yml +++ b/modules/nf-core/trimgalore/environment.yml @@ -6,4 +6,3 @@ channels: dependencies: - bioconda::trim-galore=0.6.10 - bioconda::cutadapt=5.2 - - conda-forge::python=3.12.12 diff --git a/modules/nf-core/trimgalore/tests/main.nf.test b/modules/nf-core/trimgalore/tests/main.nf.test index ac97b087142b..0833eafd25db 100644 --- a/modules/nf-core/trimgalore/tests/main.nf.test +++ b/modules/nf-core/trimgalore/tests/main.nf.test @@ -27,7 +27,7 @@ nextflow_process { { assert snapshot( process.out.reads, file(process.out.log[0][1]).readLines()[0..9], // line 11 changes - file(process.out.log[0][1]).readLines()[11..59], + file(process.out.log[0][1]).readLines()[11..59].findAll { !it.startsWith("This is cutadapt") }, // strip python version process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) @@ -56,9 +56,9 @@ nextflow_process { process.out.reads[0][1][0], process.out.reads[0][1][1], file(process.out.log[0][1][0]).readLines()[0..9], // line 11 changes - file(process.out.log[0][1][0]).readLines()[11..58], + file(process.out.log[0][1][0]).readLines()[11..58].findAll { !it.startsWith("This is cutadapt") }, // strip python version file(process.out.log[0][1][1]).readLines()[0..9], // line 11 changes - file(process.out.log[0][1][1]).readLines()[11..60], + file(process.out.log[0][1][1]).readLines()[11..60].findAll { !it.startsWith("This is cutadapt") }, // strip python version process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) @@ -95,9 +95,9 @@ nextflow_process { process.out.unpaired[0][1][0], process.out.unpaired[0][1][1], file(process.out.log[0][1][0]).readLines()[0..9], // line 11 changes - file(process.out.log[0][1][0]).readLines()[11..59], + file(process.out.log[0][1][0]).readLines()[11..59].findAll { !it.startsWith("This is cutadapt") }, // strip python version file(process.out.log[0][1][1]).readLines()[0..9], // line 11 changes - file(process.out.log[0][1][1]).readLines()[11..63], + file(process.out.log[0][1][1]).readLines()[11..63].findAll { !it.startsWith("This is cutadapt") }, // strip python version process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) diff --git a/modules/nf-core/trimgalore/tests/main.nf.test.snap b/modules/nf-core/trimgalore/tests/main.nf.test.snap index 2fb55f2d3936..8447b4bc2bdc 100644 --- a/modules/nf-core/trimgalore/tests/main.nf.test.snap +++ b/modules/nf-core/trimgalore/tests/main.nf.test.snap @@ -70,7 +70,6 @@ "Output file will be GZIP compressed", "", "", - "This is cutadapt 5.2 with Python 3.12.12", "Command line parameters: -j 1 -e 0.1 -q 20 -O 1 -a AGATCGGAAGAGC test_1.fastq.gz", "Processing single-end reads on 1 core ...", "", @@ -133,7 +132,6 @@ "Output file will be GZIP compressed", "", "", - "This is cutadapt 5.2 with Python 3.12.12", "Command line parameters: -j 1 -e 0.1 -q 20 -O 1 -a AGATCGGAAGAGC test_2.fastq.gz", "Processing single-end reads on 1 core ...", "", @@ -254,7 +252,6 @@ "Output file will be GZIP compressed", "", "", - "This is cutadapt 5.2 with Python 3.12.12", "Command line parameters: -j 1 -e 0.1 -q 20 -O 1 -a AGATCGGAAGAGC test_1.fastq.gz", "Processing single-end reads on 1 core ...", "", @@ -316,7 +313,6 @@ "Output file will be GZIP compressed", "", "", - "This is cutadapt 5.2 with Python 3.12.12", "Command line parameters: -j 1 -e 0.1 -q 20 -O 1 -a AGATCGGAAGAGC test_2.fastq.gz", "Processing single-end reads on 1 core ...", "", @@ -407,7 +403,6 @@ "Output file will be GZIP compressed", "", "", - "This is cutadapt 5.2 with Python 3.12.12", "Command line parameters: -j 1 -e 0.1 -q 20 -O 1 -a AGATCGGAAGAGC test.fastq.gz", "Processing single-end reads on 1 core ...", "",