Fixes for Transmission 4.1.x on macOS Tahoe 26.x (also applies to Sequoia 15.x): torrent stalls and hang on quit.
- Latest stable Transmission 4.1.2 (2026-06-02) contains no fix for the quit hang (
tr_sessionClose()still waits unconditionally) and nothing uTP/ECN-related (verified against the 4.1.1…4.1.2 diff). The workarounds and patches here are still needed. - 4.1.2 does ship #8748 — a cherry-pick of #8745 (TCP segments ≥128 bytes), which closed #8308 "no downloading on Mac" — a related but distinct connectivity bug. If you see stalls on ≤4.1.1, upgrade to 4.1.2 first; the fixes here address what remains.
- Upstream fix for the quit hang: PR #8632 — open, awaiting review (revised 2026-06-11; see Code fix).
- #7642 (quit hang, labeled "pr welcome") — open. #8305 (slow speeds on Mac) — open.
- Verified on macOS Tahoe 26.4 Beta 2 through 26.6.
Root cause (as established by local testing — see caveat below): macOS Tahoe enables ECN (net.inet.tcp.ecn_initiate_out=1) by default. Some BitTorrent peers, NAT routers, and middleboxes drop or ignore ECN-marked TCP SYN packets, causing connections to time out or cycle between brief activity and stalls.
Additionally, uTP (UDP-based transport) connections in Transmission 4.1.x die after ~2 minutes on Tahoe even with ECN disabled — disabling uTP, DHT, and UPnP/NAT-PMP is what decisively stabilizes peer connections.
Caveat: the ECN hypothesis is based on testing on one machine (disabling ECN measurably reduced stalls); it is not confirmed by Apple or Transmission upstream. The uTP/DHT/UPnP disabling (preflight Phase 1.6) was the most impactful fix in our testing.
Transmission issues: #8305, #8308
Root cause: tr_sessionClose() calls closed_future.wait(), which blocks indefinitely. The session thread may be stuck in a kernel-level connect() call (exacerbated by ECN) or in disk I/O on external drives. The main thread waits forever for the session thread to signal completion.
Transmission issues: #7642 (pr welcome), #7784, #4759, #6654
| Script | Purpose |
|---|---|
transmission-preflight.sh |
Apply all OS fixes before launching Transmission |
transmission-enhanced-monitor.sh |
Real-time monitoring with quit-hang detection and auto-spindump |
transmission-safe-quit.sh |
Safe quit: pause torrents → drain connections → quit → SIGTERM/SIGKILL fallback |
com.local.disable-ecn.plist |
LaunchDaemon to persist the ECN fix across reboots |
Quick start:
# Apply fixes (requires sudo for sysctl/pmset)
sudo bash scripts/transmission-preflight.sh
# Install persistent ECN fix
sudo cp scripts/com.local.disable-ecn.plist /Library/LaunchDaemons/
sudo launchctl bootstrap system /Library/LaunchDaemons/com.local.disable-ecn.plist
# Monitor Transmission (sudo recommended: spindump needs root)
sudo bash scripts/transmission-enhanced-monitor.sh 3
# Safe quit when needed
bash scripts/transmission-safe-quit.shOptional: if you download to an external drive, export TORRENT_VOLUME=<VolumeName> (the name under /Volumes/) — the preflight will warn when the volume is missing, and the monitor will report per-volume disk I/O.
The monitor writes its log and spindumps to a private per-user directory: $TMPDIR/transmission-samples-<uid>/ (the exact paths are printed at startup).
| Patch | Target |
|---|---|
0001-fix-quit-hang-add-timeout-to-session-shutdown.patch |
upstream main — identical to PR #8632 |
0002-backport-quit-hang-timeout-4.1.2.patch |
the 4.1.2 stable release tag |
Replaces closed_future.wait() with closed_future.wait_for(timeout + 5s) in tr_sessionClose(). If the session thread does not complete within the window, a warning is logged and the function returns without freeing the session or the promise — the stuck session thread may still touch both, so freeing them would be a use-after-free. They are intentionally leaked; the process is exiting anyway. The promise is heap-allocated so it can outlive the function's stack frame on that path. The normal quit path is unchanged.
git clone https://github.com/transmission/transmission.git
cd transmission && git checkout 4.1.2
git apply /path/to/patches/0002-backport-quit-hang-timeout-4.1.2.patchBoth patches are compile-verified (libtransmission target, clang, macOS arm64).
| Fix | Setting | Default | Fixed | Persistent |
|---|---|---|---|---|
| Disable ECN | net.inet.tcp.ecn_initiate_out + net.inet.tcp.ecn |
1 | 0 | Via LaunchDaemon |
| Disable disk sleep | pmset disksleep |
10 | 0 | No (reboot resets) |
| Disable App Nap | NSAppSleepDisabled |
not set | YES | Yes |
| Reduce peers | PeersTorrent/PeersTotal |
120/400 | 60/200 | Yes |
| Reduce TCP connect timeout | net.inet.tcp.keepinit |
75000ms | 10000ms | No (reboot resets) |
| Disable uTP | UTPGlobal |
YES | NO | Yes |
| Disable DHT | DHTGlobal |
YES | NO | Yes |
| Disable UPnP/NAT-PMP | NatTraversal |
YES | NO | Yes |
All defaults write org.m0k.transmission settings persist across reboots; sysctl/pmset values reset on reboot (hence the LaunchDaemon for ECN, or re-run the preflight).
- macOS Tahoe 26.4 Beta 2 → 26.6, Apple silicon
- Transmission 4.1.1 (build 56442e2929)
- Downloads to an external Thunderbolt SSD (APFS)
Scripts: MIT (see LICENSE). Patches under patches/ are derived from Transmission and follow its license (GPL-2.0-or-later).