Skip to content

Fix intermittent suspend hang on T2 MacBooks (force synchronous device suspend)#6149

Open
andyhoops wants to merge 3 commits into
basecamp:devfrom
andyhoops:fix-t2-suspend-async
Open

Fix intermittent suspend hang on T2 MacBooks (force synchronous device suspend)#6149
andyhoops wants to merge 3 commits into
basecamp:devfrom
andyhoops:fix-t2-suspend-async

Conversation

@andyhoops

Copy link
Copy Markdown

Refs: #1840

Problem

On T2 MacBooks, systemctl suspend / lid-close intermittently hard-hangs the
machine the instant it enters suspend — black screen, dead keyboard/trackpad/
Touch Bar, no wake; only a forced power-off recovers. This is the core of the
widely-reported #1840 across many T2 models (13"/15"/16", 2018–2020).

Root cause

An asynchronous device-suspend race. The kernel default
(/sys/power/pm_async=1) suspends devices in parallel; on T2 hardware this
races and deadlocks. pm_trace localises the hang to the async suspend core
(drivers/base/power/main.c), not to any single driver — which is why
unloading individual drivers (brcmfmac, apple-bce) and PCI D3cold tweaks don't
help. Forcing synchronous device suspend (pm_async=0) eliminates the race.

The existing install/config/hardware/apple/fix-suspend-nvme.sh does not cover
these machines: it excludes 16,x/15,x models and targets a discrete NVMe at PCI
0000:01:00.0, whereas 16,2-class storage is the T2-bridged ANS2 controller at
e6:00.0. This fix is independent of that one.

Fix

Add install/config/hardware/apple/fix-suspend-async.sh: on T2 Macs
(lspci 106b:180[12]), install + enable a oneshot service that writes
0 to /sys/power/pm_async at boot. Mirrors the structure of the existing
fix-suspend-nvme.sh. No change to mem_sleep / logind.conf / sleep.conf /
bootloader cmdline is required.

Testing

  • MacBookPro16,2, Omarchy 3.8.2, linux-t2 7.0.12: 5/5 clean suspend/resume
    cycles with the fix vs 5/5 hard hangs without (same deep mode). Lid
    close/reopen confirmed. Persists across reboot.
  • Low risk: serial device suspend is marginally slower (sub-second) and is a
    no-op on hardware that wasn't racing.

Changes

  1. install/config/hardware/apple/fix-suspend-async.sh (new)
  2. install/config/all.sh — add one line after the fix-suspend-nvme.sh entry:
    run_logged $OMARCHY_INSTALL/config/hardware/apple/fix-suspend-async.sh
  3. migrations/<unix-timestamp>.sh (new) — sources the script so existing
    installs get it on omarchy update.

Note for reviewers

Gated to T2 Macs (where it's proven). pm_async=0 is harmless on non-racing
hardware, so the gate could be widened to all Apple MacBooks (#1840 includes a
couple of non-T2 reports) if preferred. Co-testers from #1840 on 13"/15" models
would help confirm breadth.

Copilot AI review requested due to automatic review settings June 29, 2026 14:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses intermittent hard hangs during suspend on T2 MacBooks by forcing synchronous device suspend (setting /sys/power/pm_async=0) via a boot-time systemd oneshot service, and ensures existing installations receive the fix via a migration.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Changes:

  • Add a T2-gated hardware script that installs/enables a systemd oneshot service to set pm_async=0 at boot.
  • Wire the new script into the standard install/config stage.
  • Add a migration to apply the same fix to existing installs on update.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
migrations/1782742549.sh Runs the new Apple T2 suspend fix for existing installs via migration.
install/config/hardware/apple/fix-suspend-async.sh New T2-detection script that installs/enables a oneshot systemd unit to set pm_async=0.
install/config/all.sh Invokes the new Apple suspend async fix during install/config.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread migrations/1782742549.sh Outdated
Comment on lines +1 to +11
# Omarchy migration — apply the T2 synchronous device-suspend fix to existing
# installs. At submission time, RENAME this file to a fresh unix timestamp that
# is greater than the newest existing migration, e.g.:
#
# mv migration.sh "migrations/$(date +%s).sh"
#
# (Verify it sorts after the current latest in migrations/.)

echo "Fix intermittent suspend hang on T2 MacBooks (force synchronous device suspend)"

source $OMARCHY_PATH/install/config/hardware/apple/fix-suspend-async.sh

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0fe6d3f — migration now leads with the echo like the others

Comment on lines +16 to +26
cat <<EOF | sudo tee /etc/systemd/system/omarchy-pm-async-suspend-fix.service >/dev/null
[Unit]
Description=Omarchy synchronous device-suspend fix for T2 MacBook

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo 0 > /sys/power/pm_async'

[Install]
WantedBy=multi-user.target
EOF

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added ConditionPathExists=/sys/power/pm_async in 0fe6d3f, thanks.

WantedBy=multi-user.target
EOF

chrootable_systemctl_enable omarchy-pm-async-suspend-fix.service

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script is also sourced by the migration, but chrootable_systemctl_enable is only defined when the installer loads install/helpers/all.sh. omarchy-migrate runs each migration in a fresh shell, so the function is unavailable on existing installations.

On a T2 Mac, this produces chrootable_systemctl_enable: command not found. The following systemctl daemon-reload can still succeed, causing the migration to exit successfully and be marked complete even though the service was never enabled or started.

Could you make this script self-contained for the migration path, or explicitly load the helper and ensure an enable failure propagates as a non-zero exit?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thank you — that's a real hole and it would have hit every existing
T2 install on omarchy update, silently. Fixed in 1faf6a0.

I went with your second option, loading the helper rather than inlining the
chroot branch, so the chroot logic stays in one place:

# The installer defines this helper via install/helpers/all.sh, but migrations
# run in a fresh shell, so pull it in directly when it isn't already defined.
# (OMARCHY_INSTALL is installer-only; OMARCHY_PATH is in the user environment.)
declare -F chrootable_systemctl_enable >/dev/null ||
  source "$OMARCHY_PATH/install/helpers/chroot.sh"

if ! chrootable_systemctl_enable omarchy-pm-async-suspend-fix.service; then
  echo "Failed to enable omarchy-pm-async-suspend-fix.service" >&2
  exit 1
fi

sudo systemctl daemon-reload

Two notes on the details:

  • It sources via $OMARCHY_PATH/install/helpers/chroot.sh, not
    $OMARCHY_INSTALL. OMARCHY_INSTALL is exported only by install.sh, so on
    the migration path it's empty — using it would have reintroduced the same class
    of bug one level down.
  • The declare -F guard means the installer path is untouched: chroot.sh does
    export -f, so the function is already present in run_logged's subshell and
    nothing is re-sourced.

I verified both call paths with stubbed lspci/sudo/systemctlbash $file
for the migration and bash -c "source $script" to match run_logged:

case before after
migration, enable succeeds (never enabled) exit 0, enabled
migration, enable fails exit 0, marked complete exit 1, migration reports failure
installer, helper present, enable fails exit 0 exit 1
installer, OMARCHY_CHROOT_INSTALL=1 enable (no --now) unchanged
non-T2 hardware no-op no-op

However, please note that I had to revert this machine to macOS — external
displays, BLE Bluetooth and the Thunderbolt dock were all broken under T2 Linux
in ways I couldn't fix at the config or distro level. The original
5/5-clean-vs-5/5-hang result on the MacBookPro16,2 still stands for the installer
path, but I can't re-test on the hardware now, so this last change is verified by
code reading and the simulation above rather than on a live T2 Mac. If you'd
rather have a hardware confirmation of the migration path before merging, the
co-tester call is still open on #1840 and I'm happy to wait on it.

Unrelated to this PR, but noticed while tracing it: the existing
install/config/hardware/apple/fix-suspend-nvme.sh has the same latent issue —
it calls chrootable_systemctl_enable and would break identically if it were
ever sourced from a migration. No migration does today, so it's harmless, but
happy to send a follow-up applying the same guard there if you want it.

chrootable_systemctl_enable is only defined once the installer has sourced
install/helpers/all.sh. omarchy-migrate runs each migration with a fresh
'bash $file', so on existing installs the call failed with 'command not
found' and the following daemon-reload still exited 0 - the migration was
marked complete without the service ever being enabled.

Source install/helpers/chroot.sh when the helper isn't already defined
(via OMARCHY_PATH, since OMARCHY_INSTALL is installer-only), and exit
non-zero if the enable fails so omarchy-migrate reports it.
Copilot AI review requested due to automatic review settings July 25, 2026 15:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants