Summary
scripts/hamma_scrub.py::select_target_drive picks the recovery target as "the drive containing the most recent hourly directory", but the heuristic is defeated on any drive prepared under Windows, because it does not exclude the Windows artifact directories.
The bug
for entry in os.listdir(drive):
if entry == "compressed":
continue
full = os.path.join(drive, entry)
if os.path.isdir(full) and entry > most_recent:
most_recent = entry
Only compressed is excluded. Both $RECYCLE.BIN and System Volume Information are directories and both sort above any 2026-… hourly directory in ASCII (S = 0x53 beats 2 = 0x32).
So every Windows-prepared drive yields the same sort key:
| drive |
sort key |
| DATA80 |
System Volume Information |
| DATA81 |
System Volume Information |
The keys tie. Python's sort is stable, so sorted(glob.glob(pattern)) order decides — i.e. the alphabetically first drive by name, not the one with the most recent data. The hour-directory heuristic is completely inert on such drives.
Observed consequence
On mjolnir54 this handed recovery to DATA80 — which was mounted read-only — while DATA81 was the writable drive. Recovery then failed on every trigger:
Recovery: 1417 attempted, 0 succeeded, 1417 failed
FAILED: ags2026-08-06_00-08-16-939.bin trigger #34 —
[Errno 30] Read-only file system: '/media/pi/DATA80/.tmp_recover_r9bp_ys8.bin'
See sensor-log #110 for the full incident.
Suggested fix
Exclude the Windows artifacts alongside compressed, and — more robustly — only consider entries that actually match the hourly-directory shape rather than blacklisting known-bad names:
HOURLY_DIR_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}$")
...
if os.path.isdir(full) and HOURLY_DIR_RE.match(entry) and entry > most_recent:
A whitelist is preferable here: the blacklist has already been wrong twice (compressed was added reactively, the Windows dirs were missed), and any future stray directory re-breaks it.
Worth deciding separately what should happen when no drive has any hourly directory — currently they all tie at "" and the choice again falls to glob order. On a freshly replaced drive that is exactly the situation.
Related
Summary
scripts/hamma_scrub.py::select_target_drivepicks the recovery target as "the drive containing the most recent hourly directory", but the heuristic is defeated on any drive prepared under Windows, because it does not exclude the Windows artifact directories.The bug
Only
compressedis excluded. Both$RECYCLE.BINandSystem Volume Informationare directories and both sort above any2026-…hourly directory in ASCII (S= 0x53 beats2= 0x32).So every Windows-prepared drive yields the same sort key:
System Volume InformationSystem Volume InformationThe keys tie. Python's sort is stable, so
sorted(glob.glob(pattern))order decides — i.e. the alphabetically first drive by name, not the one with the most recent data. The hour-directory heuristic is completely inert on such drives.Observed consequence
On mjolnir54 this handed recovery to
DATA80— which was mounted read-only — whileDATA81was the writable drive. Recovery then failed on every trigger:See sensor-log #110 for the full incident.
Suggested fix
Exclude the Windows artifacts alongside
compressed, and — more robustly — only consider entries that actually match the hourly-directory shape rather than blacklisting known-bad names:A whitelist is preferable here: the blacklist has already been wrong twice (
compressedwas added reactively, the Windows dirs were missed), and any future stray directory re-breaks it.Worth deciding separately what should happen when no drive has any hourly directory — currently they all tie at
""and the choice again falls to glob order. On a freshly replaced drive that is exactly the situation.Related