When the destination fills up, every remaining file is still extracted, downloaded in full, and only then fails on the write. The run does not stop and the user is not told.
Observed
58 links, destination drive with no free space left:
Total links : 58
Completed OK : 12
Failed : 46
Total data : 4.36 GB
Median DL time : 52.9s
The GUI meanwhile reported 16.58 GB transferred over 4m 50s. Every one of the 46 failures carries the same note:
1 Halo_Campaign_Evolved_... 0 0 0.0 0.4 236.1 — fail
→ error att 1: [Errno 28] No space left on device
2 Halo_Campaign_Evolved_... 1 0 0.0 0.5 236.5 — fail
→ error att 1: [Errno 28] No space left on device
3 Halo_Campaign_Evolved_... 2 0 0.0 0.3 236.2 — fail
Each of those spent 236 seconds downloading before failing on a write that could never succeed. Roughly 12 GB pulled off the network and discarded.
Cause
ENOSPC is not distinguished anywhere — grep -n "ENOSPC\|No space" moon_download.py returns nothing. It lands in the catch-all:
except Exception as e:
err = str(e)
rec.notes.append(f"error att {att+1}: {err}")
if att < DL_INNER_RETRIES - 1 and ("ContentLengthError" in err or "not enough data" in err.lower()):
await asyncio.sleep(0.5 * (att + 1))
continue
return False, err, downloaded
It correctly avoids the inner retry, then returns a per-file failure and the worker moves to the next link — which will fail identically, because the disk does not empty itself between files.
The note goes to rec.notes, which only reaches the report after the run (#99), so nothing on screen ever says "disk full". The user sees files failing for no stated reason.
What is needed
ENOSPC is not a per-file problem. It is a run-level, non-recoverable condition and the only correct response is to stop:
- Detect it specifically —
isinstance(e, OSError) and e.errno == errno.ENOSPC rather than matching on the message, which is localised (the report above is from an Italian Windows and reads "No space left on device" only because Python normalises it; other paths do not)
- Abort the run rather than continuing to the next link
- Say so unmistakably, in the live log and not only in the report: which folder, how much was needed
- Leave the
.tmp files alone — they are resumable once space is freed, and the run had 46 of them
Worth deciding and stating in the pull request: whether in-flight transfers are cancelled immediately or allowed to finish. Cancelling recovers the bandwidth sooner; finishing means fewer half-files. Either is defensible.
Acceptance criteria
- A run that hits
ENOSPC stops instead of attempting every remaining link
- The reason appears on screen while it happens, naming the destination folder
- Existing
.tmp files are preserved for resume
- Detection is by
errno, not by string matching
- A regression test with a write that raises
OSError(errno.ENOSPC, ...) — no network needed, tests/test_resume_200.py shows the pattern for faking the response surface
Environment
Windows 10, Python 3.14.6, destination on a drive with 0 bytes free of 931 GB. Found by running the program.
When the destination fills up, every remaining file is still extracted, downloaded in full, and only then fails on the write. The run does not stop and the user is not told.
Observed
58 links, destination drive with no free space left:
The GUI meanwhile reported 16.58 GB transferred over 4m 50s. Every one of the 46 failures carries the same note:
Each of those spent 236 seconds downloading before failing on a write that could never succeed. Roughly 12 GB pulled off the network and discarded.
Cause
ENOSPCis not distinguished anywhere —grep -n "ENOSPC\|No space" moon_download.pyreturns nothing. It lands in the catch-all:It correctly avoids the inner retry, then returns a per-file failure and the worker moves to the next link — which will fail identically, because the disk does not empty itself between files.
The note goes to
rec.notes, which only reaches the report after the run (#99), so nothing on screen ever says "disk full". The user sees files failing for no stated reason.What is needed
ENOSPCis not a per-file problem. It is a run-level, non-recoverable condition and the only correct response is to stop:isinstance(e, OSError) and e.errno == errno.ENOSPCrather than matching on the message, which is localised (the report above is from an Italian Windows and reads "No space left on device" only because Python normalises it; other paths do not).tmpfiles alone — they are resumable once space is freed, and the run had 46 of themWorth deciding and stating in the pull request: whether in-flight transfers are cancelled immediately or allowed to finish. Cancelling recovers the bandwidth sooner; finishing means fewer half-files. Either is defensible.
Acceptance criteria
ENOSPCstops instead of attempting every remaining link.tmpfiles are preserved for resumeerrno, not by string matchingOSError(errno.ENOSPC, ...)— no network needed,tests/test_resume_200.pyshows the pattern for faking the response surfaceEnvironment
Windows 10, Python 3.14.6, destination on a drive with 0 bytes free of 931 GB. Found by running the program.