A Windows PC Backup That Never Reported Up: Robocopy /ZB, ERROR 50, and a Silent 6 h Kill
My robocopy backup to an unRAID Samba share had never once completed. How /Z, /B, a 6 hour task limit and an end-of-run monitor push hid it, and the fixes that got a full run to 10m53s.
HomeLab, Backups. Updated . 5 min read.
My Windows PC backup ran twice a day for weeks, and its monitor had never once gone up. Not down. Not failing. Just silent, because every single run was killed before it reached the line that reports.
The task looked healthy the whole time. Task Scheduler said LastTaskResult 0 on every run, because the conhost wrapper swallows the real exit code. The only honest signal was the Uptime Kuma push monitor, and it had nothing to say.
TL;DR: robocopy's /ZB flags hid two different problems, a 29 GB Thunderbird maildir could not finish over SMB inside the task's 6 h limit, and the script only pushed its status at the end. A killed run is not a failed run, it is no run at all. I dropped /Z and /B, moved the maildir to its own rsync job, added a soft deadline that pushes down, and the first run that ever completed took 10 min 53 s.
The setup
A Claude agent on that PC manages these scheduled tasks, so the changes below went through it.
A scheduled task \Personal\Backup PC runs C:\Tools\pc-backup.ps1 at 09:30 and 18:00, with run-if-missed, RunOnlyIfNetworkAvailable, and MultipleInstances=IgnoreNew. The script mirrors 9 source folders with robocopy /MIR to an SMB share on the NUC (\\nuc\<share>\pc_backup), then pushes the result to Kuma. The monitor expects one beat per 24 h.
That last detail is the whole bug. The push lives at the very end of the script. If the run never reaches the end, nothing is sent, and a push monitor with a 24 h window just waits.
Why every run died
The Thunderbird profile held about 326k files and 29 GB, almost all of it the ImapMail folder. That is the Proton Mail Bridge IMAP cache, one file per message. Robocopy over SMB could not get through it inside the task's 6 h ExecutionTimeLimit, so every run since at least 2026-08-24 was hard-killed somewhere inside Thunderbird.
It got worse. Thunderbird sat third in $sources. The six sources after it were never backed up either. So "the PC backup is flaky" was really "six of nine sources had no backup at all".
Two flags made the slow part slower.
The trap in /ZB
The original arguments used /ZB. It reads like the safe choice: restartable mode, with backup mode as a fallback. Both letters were traps against this share.
/Z (restartable) is slow over SMB, and it looped. It spent more than 4 h re-copying global-messages-db.sqlite while Thunderbird kept rewriting it. Restartable mode keeps coming back to a file that never holds still.
/B (backup mode) was worse. Forced on its own against the unRAID Samba share, it returned ERROR 50, "request not supported", while writing ordinary files like .jpg and .pdf. On the live Thunderbird SQLite files (places.sqlite, cookies.sqlite, storage.sqlite) it returned ERROR 32, sharing violation. A normal copy reads those same files without complaint.
The reason /ZB never showed this is the subtle part. /ZB copies normally and only escalates to backup mode on access denied. So the backup-mode failures stayed hidden until I tested /B alone. The evidence was clean: with /B, the C:\Users\<me> source returned exit 11. With a plain copy, exit 3.
Robocopy exit codes are a bitmask, and 8 and up means at least one file failed. 11 is failures plus copies plus extras. 3 is copies plus extras, no failures. That one number told me which flag to delete.
The arguments now:
/MIR /R:1 /W:1 /NJH /NJS /MT:32 /COPY:DAT /DCOPY:DAT /XJ /NP /FFTNo /Z, no /B. /R:1 /W:1 so a locked file costs a second, not the default million retries of 30 s. /FFT for 2-second file times, which is what the share gives back. /MT:32 instead of /MT:8.
Other exit-11 noise
The maildir was not the only thing forcing exit 11. 378 files named ._* inside __MACOSX folders, macOS junk from unzipped archives, failed with ERROR 2 on every run. They were excluded along with *cache* folders, Temp, global-messages-db.sqlite, *.msf, and the *.sqlite-wal, -shm and -journal sidecars. AppData stays excluded apart from the Thunderbird, Vivaldi and Firefox profile folders that are listed as their own sources.
One more cause of instant failure: the catch-up run at logon started before the SMB session was up. The script now waits up to 10 minutes for the share before it starts.
The fixes in the script
ImapMailis excluded from robocopy and handed to a separate job.- Thunderbird moved to last in
$sources. A slow mail sync can never again block the other eight. $maxRunHours = 5, a soft deadline an hour before the scheduler's 6 h kill. When it hits, the script stops cleanly and pushesdownwith a summary.
The third one matters most. A backup that fails loudly is a normal Tuesday. A backup that dies silently is the one that loses data, because nobody looks.
Verified on 2026-08-30: all 9 sources completed in 10 min 53 s, the first complete run this job ever had. The Thunderbird source now takes about 6 s.
Mail moved to rsync over SSH
The authoritative copy of that mail lives on Proton's servers. The local-only part, Mail/Local Folders, is 12 KB of Trash and Unsent. So the maildir is a cache worth keeping, not the only copy, and it can live with a different job and different rules.
A WSL script, sync-mail.sh, rsyncs ImapMail over SSH, not SMB to the NUC. A Windows task runs it at 10:15 and 19:00 with a 4 h limit and normal privileges, and it pushes its own Kuma monitor. The core line:
rsync -rlt --modify-window=2 --max-delete=5000 ...The script holds a flock, and it refuses to sync at all when the profile looks reset or unmounted. The flags:
--max-delete=5000: a corrupt or empty source can never wipe the server copy.--modify-window=2: robocopy seeded those files with 2 s FAT timestamps. Without this, rsync sees every file as changed.-rlt, not-a: permissions and owners on DrvFs are synthetic, and-awould rewrite all of them on every run.
Robocopy had already copied 19 GB of the maildir before it was killed each time. I moved that server-side into the new path, an instant rename on the same filesystem, so only the ~10 GB remainder had to cross the network.
The steady state is revealing: a no-op pass takes about 365 s, and nearly all of it is stat-ing 324,539 files across DrvFs. Nothing is copied. The file count alone costs six minutes. That is also why robocopy never stood a chance with this folder over SMB.
On the NUC side the maildir now sits at 30 GB and 324,539 files, and it rides along in the Deep Archive pass I set up in my Glacier backup post. That object count has a cost of its own, which is a story for another post.
The lesson I keep from this one: a status push that lives only at the end of a script turns every timeout into silence. Put a deadline before the kill, and push from there.
Questions
- Why does robocopy /B return ERROR 50 on a Samba share?
- In my case robocopy backup mode (/B) returned ERROR 50 "request not supported" when it wrote ordinary files like .jpg and .pdf to an unRAID Samba share, and ERROR 32 on live SQLite files that a normal copy read fine. Dropping /B and using a plain copy fixed both.
- Should I use robocopy /ZB for backups over SMB?
- Not by default. /Z (restartable mode) is slow over SMB and can loop on a file that keeps changing. /ZB hides backup-mode errors because it only switches to backup mode on access denied. A plain /MIR copy with /R:1 /W:1 was faster and more honest for me.
- Why did my backup monitor never go up even though the task ran every day?
- The script pushed its status only at the end of a run, and Task Scheduler killed every run at the 6 hour ExecutionTimeLimit before the end. A killed run pushes nothing, so the push monitor stayed silent instead of going down. A soft deadline that stops cleanly and pushes down before the hard limit fixes that.
- How do I back up a large Thunderbird IMAP cache from Windows?
- Take it out of the robocopy job. I excluded ImapMail and sync it with rsync over SSH from WSL, with flock, a sanity check on the profile, --max-delete, --modify-window=2 and -rlt instead of -a.