macOS SMB: OSError: [Errno 9] Bad file descriptor after a silent reconnect
A Python script that had encrypted backup archives on a NAS for months started failing on every run:
Traceback (most recent call last):
File "/Volumes/backup/./encrypt-one.py", line 74, in <module>
main()
File "/Volumes/backup/./encrypt-one.py", line 38, in main
with open(f, "rb") as src:
OSError: [Errno 9] Bad file descriptor
Then the bash script that called it stopped too. Not intermittently: start the job and something fails sooner or later.
If you run long jobs from an SMB share on macOS and get EBADF on files that are plainly there, the cause is probably a session reconnect you never saw, and the kernel log is the only place that records it.
The setup #
macOS 27 dropped AFP, so an old NAS that speaks only SMB 2.1 got mounted over SMB instead. A bash script on the share runs encrypt-one.py on a few archives in parallel with xargs -P. Each run streams a 600 MB archive from the share through openssl enc and writes the result back to the same share.
The dead end #
The traceback points at open(), so the first move was to open and read that archive by hand. It worked, and so did a read under xargs -P 3. A file that opens fine and a job that fails anyway tells you the file isn’t the problem. Something happens to the handle while it’s open.
The kernel log #
smbutil gave the first hint:
$ smbutil statshares -m /Volumes/backup
...
SMB_VERSION SMB_2.1
SESSION_RECONNECT_TIME 2026-09-29 12:01:17
SESSION_RECONNECT_COUNT 4
Four reconnects; the last one was inside the failed run. The kernel log explains what each one does (timestamps shortened, process column removed):
/usr/bin/log show --last 1h --style compact \
--predicate 'process == "kernel" AND eventMessage CONTAINS[c] "smb"'
11:58:15.502 (smbfs) smb_iod_sendall: id 0 ts > iod_lastrecv && ts > sr_timesent.
11:58:15.502 (smbfs) smb_iod_reconnect: id 0 Starting reconnect with nas.local
11:58:15.502 (smbfs) smb2_smb_parse_change_notify: smb_rq_reply failed 60
11:58:16.523 (smbfs) smb_iod_reconnect: id 0 The negotiate succeeded to nas.local
11:58:17.065 (smbfs) smb2fs_reconnect_dur_handle: Missing durable handle on <archive.tar.bz2.enc.tmp>
11:58:17.173 (smbfs) smb2fs_reconnect_dur_handle: Missing durable handle on <backup.sh>
11:58:17.186 (smbfs) smb2fs_reconnect_dur_handle: Missing durable handle on <archive.tar.bz2>
11:58:17.205 (smbfs) smb_iod_reconnect: id 0: Reconnect completed successfully.
11:58:17.212 (smbfs) smb2_smb_read_write_async: smb_rq_reply failed 9
11:58:17.212 (smbfs) smbfs_do_strategy: archive.tar.bz2: READ failed with an error of 9
11:58:17.283 (smbfs) smbfs_do_strategy: archive.tar.bz2.enc.tmp: WRITE failed with an error of 9
The NAS stopped answering under sustained I/O (error 60 is ETIMEDOUT). macOS dropped the session and reconnected, and that part worked. Then it tried to reattach each open file; the server had nothing to reattach them to, and every read and write on those files failed with error 9, EBADF.
Look at the middle line: backup.sh is on the list. Bash reads a script from disk as it runs, so the script file is open the whole time and goes bad with everything else. That’s why the shell stopped as well.
Why #
A durable handle is how an SMB 2.x client keeps an open file across a dropped connection: the server holds the open for a while, and the client reclaims it after reconnecting. This server granted none. So “Reconnect completed successfully” is true for the session and useless for every file that was open at the time. Nothing reaches the application except EBADF on the next read or write.
What fixed it #
Stop running long I/O jobs from the share. The pipeline moved to a USB drive, and only finished files go to the NAS. That ended the failures.
If you can’t move off the share, two workarounds got the job past the failing step:
Make bash read the whole script before it starts, so it never goes back to the share for the next line:
#!/usr/bin/env bash { set -euo pipefail # ... the whole script ... exit }Bash parses the entire
{ }block before running any of it, and theexitstops it from reading past the closing brace.Make the I/O survive a reconnect: process the file in segments (64 MiB worked), open, write and
fsynceach segment separately, and onEBADF,EIOorEAGAINreopen and retry only that segment. With AES-CBC you can chain the segments by using the last ciphertext block as the next IV, and the output is byte-identical to oneopenssl encpass. In the test run on the share, no reconnect happened at all, probably because the per-segmentfsyncslowed the writes enough for the NAS to keep up.
If the server is a Samba box you control, check whether it grants durable handles: the durable handles option only takes effect with kernel oplocks = no, kernel share modes = no and posix locking = no. Turning off SMB signing on the client (signing_required=no in /etc/nsmb.conf) might also take enough load off a weak NAS CPU to prevent the stalls. Neither was tried here.
Takeaway #
On macOS, an SMB reconnect to a server without durable handles invalidates every open file on the share, including a running shell script, and the only trace is Missing durable handle in the kernel log.
If you’ve seen Errno 9 on an SMB mount, run smbutil statshares -a and look at SESSION_RECONNECT_COUNT before you debug your code.