Skip to content

Repository files navigation

dock-panic-guard

Prevent macOS kernel panics when your Mac goes to sleep while a Thunderbolt dock with built-in mass storage is attached.

This is a small, event-driven macOS LaunchDaemon. When you connect a compatible Thunderbolt dock it switches the AC power profile to sleep 0 / standby 0; when you disconnect the dock it restores the values you had right before attaching, unless you changed them yourself while the dock was connected (each setting is restored independently — your edits are never overwritten). Detection is event-driven via IOKit notifications: no polling.

Why this exists

On macOS 26A428 (and adjacent builds), this MacBook Pro 14-inch M1 Max panic-crashed on system sleep while an Orico Thunderbolt 3 dock was attached:

IOSCSITargetDevice::setPowerState(1 -> 0) timed out after ~101s @IOServicePM.cpp:5678

The root cause chain: the dock exposes a built-in USB mass storage device through an external (Intel) USB controller. On sleep, macOS's IOKit power cascade times out while putting that device's SCSI target to sleep, and the kernel panics.

pmset disksleep 0 does not help: disksleep only governs disk spin-down while the system is awake; system sleep performs a separate IOKit power cascade. The direct workaround is to prevent system sleep while the dock is attached: pmset -c sleep 0 (and the related standby 0). This daemon does that automatically, event-driven, and restores your previous settings afterwards.

How it works (event-driven, no polling)

  • Registers IOServiceAddMatchingNotification (IOServiceFirstMatch) for IOUSBMassStorageInterfaceNub — exactly the node that produces IOSCSITargetDevice and triggers the panic.
  • A dock counts as attached when at least one USB Mass Storage service has an ancestor whose class or registry name contains any pattern from /usr/local/etc/dock-panic-guard.conf (default: pci8086,15f0 — the dock's Intel USB controller — and IOPCI2PCIBridge — the PCI bridge).
  • Matching is case-insensitive and walks the whole ancestor chain (not just the nearest parent), which tolerates different controller/bridge variants.
  • Built-in MacBook USB ports (Apple AppleT6000USBXHCI) never produce that chain, so a flash drive plugged directly into the Mac is not treated as a dock.
  • On match, it also registers an interest notification (kIOGeneralInterest) on the concrete mass storage service.
  • Disconnection is delivered as kIOMessageServiceIsTerminated on the interest notification; the daemon restores the saved pmset values.
  • Note: IOServiceTerminated via IOServiceAddMatchingNotification is unsupported on macOS 27 (kIOReturnUnsupported), so detach is handled through the interest notification.

Save / restore logic

  • sleep and standby of the AC profile are read and written to the state file at dock-attach time, not at daemon start. If you changed settings before attaching, those are the values restored on detach.
  • Only sleep and standby are set (to 0). disksleep and every other AC parameter are left untouched.
  • On detach, each parameter is restored independently: it is restored to the saved value only if it is still 0 (i.e. the user did not touch it while the dock was attached). If you changed, say, sleep yourself, it stays as you set it; the untouched standby is still restored.
  • A full AC profile snapshot is written to /usr/local/var/dock-power-state.ac for diagnostics only (never read by the daemon; safe to delete — it is recreated on the next attach).
  • State file: /usr/local/var/dock-power-state.state (dock, saved_sleep, saved_standby).

Booting with the dock already attached

  • Dock not attached → nothing to do; the daemon just waits for events.
  • Dock attached:
    • if current sleep/standby are already 0 and saved values exist → "continuation" (the Mac shut down/rebooted with the dock attached) → nothing is saved or changed;
    • otherwise → "fresh" start: save current sleep/standby, then apply 0/0.

This avoids relying on whether the daemon wrote state before shutdown (crash/power loss): the decision is based on "values already 0/0 + saved values present".

Repository layout

File Purpose
dock-panic-guard.c Daemon source (C, IOKit + CoreFoundation)
dock-panic-guard.conf Pattern config template (installed to /usr/local/etc/, only if absent)
com.local.dock-panic-guard.plist Root LaunchDaemon: RunAtLoad + KeepAlive
install.sh Install (sudo): build, snapshot AC values, install binary + service
uninstall.sh Rollback (sudo): stop service, remove files, restore pmset from snapshot
README.md This document
LICENSE MIT

The binary is not committed; install.sh builds it locally.

Install

git clone <your-repo-url> dock-panic-guard
cd dock-panic-guard
sudo bash install.sh

Service label: system/com.local.dock-panic-guard

Configuration

File: /usr/local/etc/dock-panic-guard.conf.

File format

One line — one case-insensitive substring, matched in the class or registry name of any node of the Mass Storage ancestor chain. Blank lines and lines starting with # are ignored.

# Example: Orico TB3 Dock
pci8086,15f0
IOPCI2PCIBridge

If the file is missing or empty, built-in values are used:

pci8086,15f0
IOPCI2PCIBridge

install.sh installs the default config only if it does not exist — your edits are never overwritten.

Finding your dock's identifier

  1. Connect the dock.
  2. Inspect the Mass Storage chain:
ioreg -r -c IOUSBMassStorageInterfaceNub -l

or (with ancestors):

ioreg -r -l | grep -B2 -A20 'IOUSBMassStorageInterfaceNub'
  1. Look for a node that is characteristic of the dock: USB controller or PCI bridge names, a vendor/device ID such as pci8086,15f0, the dock chip model.
  2. Add one substring per line. Multiple patterns are allowed — any match triggers.

Avoid too-broad patterns

Matching is done against any ancestor, so generic strings (Intel, USB, Hub) cause false positives on ordinary USB devices and enable the protection without a real dock. Use specific identifiers.

After editing the config

sudo launchctl kickstart -k system/com.local.dock-panic-guard
log show --predicate 'eventMessage CONTAINS "dock-panic-guard"' --last 5m

The log shows how many patterns were loaded and attach/detach events.

Verify

# Service status
sudo launchctl print system/com.local.dock-panic-guard | head

# While the dock is attached, AC must show sleep 0 / standby 0
pmset -g custom

# Logs
log show --predicate 'eventMessage CONTAINS "dock-panic-guard"' --last 1h

Observed log from a real attach/detach cycle:

dock attached: saved sleep=1 standby=1
pmset -c sleep 0 -> rc=0
pmset -c standby 0 -> rc=0
...
dock detached: saved sleep=1 standby=1, current sleep=0 standby=0, restoreSleep=1 restoreStandby=1
pmset -c sleep 1 -> rc=0
pmset -c standby 1 -> rc=0

Per-parameter case (user edited sleep to 5 while the dock was attached):

dock detached: saved sleep=1 standby=1, current sleep=5 standby=0, restoreSleep=0 restoreStandby=1
  sleep: left as-is (user changed or no saved value)
pmset -c standby 1 -> rc=0

Uninstall

sudo bash uninstall.sh
  1. Stop and unload the service.
  2. If the daemon had applied AC values (dock was attached), restore sleep and standby from /usr/local/var/dock-power-state.before (disksleep is untouched).
  3. Remove the plist, binary, log, state file and config.
  4. The dock-power-state.before snapshot is kept — delete it manually if you want.

Limitations

  • AC profile only (-c); the battery profile is never touched.
  • Running on battery with the dock attached: the panic may still occur on battery sleep, since the daemon does not modify the battery profile (out of scope by design).
  • Closing the lid (clamshell) initiates sleep even with sleep 0; if the panic reproduces there, it requires a different approach.
  • This is a workaround, not a kernel fix — please also file a report with Apple (Feedback Assistant).

Tested on

  • Machine: MacBook Pro (14-inch, 2021), Apple M1 Max, 64 GB — MacBookPro18,4
  • OS: macOS 27.0 (build 26A428)
  • Dock: Orico Thunderbolt 3 dock (Intel JHL8440, "Tamales Module 2"), exposing:
    • an external Thunderbolt switch (IOThunderboltSwitchType3),
    • an internal Intel USB controller (PCI ID pci8086,15f0),
    • a built-in USB mass storage device.
  • Verified behavior:
    • panic reproduced on system sleep with the dock attached;
    • attach → sleep 0 / standby 0; detach → values restored;
    • reboot with the dock already attached → daemon starts automatically, sees the already-protected values and does not re-apply;
    • user edited sleep while attached → the edit is preserved, the untouched standby is still restored;
    • a Thunderbolt Ethernet dock without built-in storage did not trigger the panic and is correctly ignored by the daemon.

License

MIT — see LICENSE.

About

Prevent macOS kernel panic (IOSCSITargetDevice) on sleep when a Thunderbolt dock with built-in mass storage is attached.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages