From 6f0ae648e3505b7f81fe2b428135e2c4ced39516 Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:09:33 +0700 Subject: [PATCH 1/2] Raise a clear error for an empty command line (#1618) A CommandLineTool with no baseCommand, arguments, or input bindings produces an empty command list. This was passed straight to subprocess.Popen([]), which crashed with a raw "IndexError: list index out of range" deep inside subprocess. Guard the empty case in JobBase._execute and raise a WorkflowException with an actionable message instead. The existing handler turns this into a clean permanentFail rather than an unhandled traceback. Co-Authored-By: Claude Opus 4.8 --- cwltool/job.py | 6 ++++++ tests/test_examples.py | 9 +++++++++ tests/wf/no-basecommand.cwl | 8 ++++++++ 3 files changed, 23 insertions(+) create mode 100644 tests/wf/no-basecommand.cwl diff --git a/cwltool/job.py b/cwltool/job.py index 9dd5b6662..06cbbb502 100644 --- a/cwltool/job.py +++ b/cwltool/job.py @@ -307,6 +307,12 @@ def stderr_stdout_log_path( stderr_path = stderr_stdout_log_path(self.base_path_logs, self.stderr) stdout_path = stderr_stdout_log_path(self.base_path_logs, self.stdout) commands = [str(x) for x in runtime + self.command_line] + if not commands: + raise WorkflowException( + "Cannot run a CommandLineTool that produces an empty command line. " + "Specify a 'baseCommand', 'arguments', and/or an input with an " + "'inputBinding' so there is a program to execute." + ) if runtimeContext.secret_store is not None: commands = cast( list[str], diff --git a/tests/test_examples.py b/tests/test_examples.py index 28245b905..9cc9f5e25 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -990,6 +990,15 @@ def test_glob_expr_error(tmp_path: Path) -> None: assert "Resolved glob patterns must be strings" in stderr +def test_empty_command_line_error() -> None: + """A CommandLineTool with an empty command line fails cleanly, not with an IndexError.""" + error_code, _, stderr = get_main_output([get_data("tests/wf/no-basecommand.cwl")]) + assert error_code != 0 + stderr = re.sub(r"\s\s+", " ", stderr) + assert "empty command line" in stderr + assert "IndexError" not in stderr + + def test_format_expr_error() -> None: """Better format expression error.""" error_code, _, stderr = get_main_output( diff --git a/tests/wf/no-basecommand.cwl b/tests/wf/no-basecommand.cwl new file mode 100644 index 000000000..657fda743 --- /dev/null +++ b/tests/wf/no-basecommand.cwl @@ -0,0 +1,8 @@ +#!/usr/bin/env cwl-runner +# Regression fixture for https://github.com/common-workflow-language/cwltool/issues/1618 +# A CommandLineTool with no baseCommand/arguments/input bindings produces an empty +# command line; running it should fail with a clear error, not a raw IndexError. +cwlVersion: v1.2 +class: CommandLineTool +inputs: [] +outputs: [] From f004621a35387da33639a23a23bbd273bc3524a8 Mon Sep 17 00:00:00 2001 From: "Michael R. Crusoe" Date: Thu, 25 Jun 2026 15:50:59 +0200 Subject: [PATCH 2/2] confirm that containerized tools don't trigger the empty commands error --- tests/test_examples.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_examples.py b/tests/test_examples.py index 9cc9f5e25..c79f246f7 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -999,6 +999,17 @@ def test_empty_command_line_error() -> None: assert "IndexError" not in stderr +@needs_docker +def test_empty_command_line_container_noerror() -> None: + """A CommandLineTool with an empty command line fails cleanly, not with an IndexError.""" + error_code, _, stderr = get_main_output( + ["--default-container", "debian:stable-slim", get_data("tests/wf/no-basecommand.cwl")] + ) + assert error_code == 0 + assert "empty command line" not in stderr + assert "IndexError" not in stderr + + def test_format_expr_error() -> None: """Better format expression error.""" error_code, _, stderr = get_main_output(