Skip to content

Commit 323ea61

Browse files
arimu1claude
andcommitted
Clearer error when a job hits a full disk (ENOSPC) (#1899)
When the temporary or output directory fills up mid-job, the OSError (errno 28, ENOSPC) fell through to the generic 'Exception while running job' handler and dumped a traceback, giving the user no idea the real problem was a full disk. Add a dedicated ENOSPC branch that logs an actionable message naming the tmpdir/outdir and pointing at --tmpdir-prefix / --outdir. Also switch the existing errno == 2 check to the named errno.ENOENT for clarity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5634342 commit 323ea61

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

cwltool/job.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
import errno
23
import functools
34
import itertools
45
import logging
@@ -376,7 +377,7 @@ def stderr_stdout_log_path(
376377
outputs = self.collect_outputs(self.outdir, rcode)
377378
outputs = bytes2str_in_dicts(outputs) # type: ignore
378379
except OSError as e:
379-
if e.errno == 2:
380+
if e.errno == errno.ENOENT:
380381
if runtime:
381382
_logger.error(
382383
"'%s' not found: %s", runtime[0], str(e), exc_info=runtimeContext.debug
@@ -388,6 +389,16 @@ def stderr_stdout_log_path(
388389
str(e),
389390
exc_info=runtimeContext.debug,
390391
)
392+
elif e.errno == errno.ENOSPC:
393+
_logger.error(
394+
"[job %s] No space left on device. The temporary directory (%s) "
395+
"and/or output directory (%s) may be full; free up space, or point "
396+
"--tmpdir-prefix and --outdir at a location with more capacity.",
397+
self.name,
398+
self.tmpdir,
399+
self.outdir,
400+
exc_info=runtimeContext.debug,
401+
)
391402
else:
392403
_logger.exception(
393404
"Exception while running job: %s", str(e), exc_info=runtimeContext.debug

tests/test_examples.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import errno
12
import json
23
import logging
34
import os
@@ -1593,6 +1594,29 @@ def test_bad_basecommand(factor: str) -> None:
15931594
assert error_code == 1
15941595

15951596

1597+
def test_disk_full_error_message(monkeypatch: pytest.MonkeyPatch) -> None:
1598+
"""A full disk (ENOSPC) while running a job yields a clear message, not a traceback."""
1599+
1600+
def _raise_enospc(*args: Any, **kwargs: Any) -> Any:
1601+
raise OSError(errno.ENOSPC, "No space left on device")
1602+
1603+
# Inject ENOSPC at the output-collection stage of a normal job run. This patches
1604+
# the method dispatched through self.collect_output_ports (and thus the
1605+
# functools.partial assigned to JobBase.collect_outputs), rather than the
1606+
# bytes2str_in_dicts free function: under a mypyc-compiled build, job.py's direct
1607+
# call to bytes2str_in_dicts is resolved to a native call that bypasses the module
1608+
# dict, so monkeypatching it there is silently ineffective.
1609+
monkeypatch.setattr(
1610+
"cwltool.command_line_tool.CommandLineTool.collect_output_ports", _raise_enospc
1611+
)
1612+
error_code, stdout, stderr = get_main_output([get_data("tests/echo.cwl"), "--inp", "hello"])
1613+
stderr = re.sub(r"\s\s+", " ", stderr)
1614+
assert "No space left on device" in stderr, stderr
1615+
assert "--tmpdir-prefix" in stderr, stderr
1616+
assert "Traceback (most recent call last)" not in stderr, stderr
1617+
assert error_code == 1
1618+
1619+
15961620
@needs_docker
15971621
@pytest.mark.parametrize("factor", test_factors)
15981622
def test_bad_basecommand_docker(factor: str) -> None:

0 commit comments

Comments
 (0)