Files
omarchycn/bin/omarchy-migrate-notify
T
David Heinemeier HanssonandClaude Opus 5 b6e1a43278 Detach the pending-migrations toast from the notifier
omarchy-migrate-notify handed its notification to `systemd-run --scope`,
which is synchronous: the calling process becomes the payload, so
systemd-run does not return until the toast has been answered -- and if
the toast is clicked, not until the migration terminal it opens has been
closed.

omarchy-migrate-notify.service is Type=oneshot, which defaults to
TimeoutStartUSec=infinity, so the unit sat in activating for exactly that
long. On a machine that had not yet picked up c7e327b0, which orders the
notifier after graphical-session.target, that held the target open --
and wayland-wm-app-daemon.service is ordered after the same target, so it
never started. Every keybinding goes through uwsm-app, which waits ten
seconds for that daemon's pipes and then reports "App failure -- Timed
out waiting for pipes!" instead of launching anything. Keybindings were
dead for two minutes and seventeen seconds, until a pacman hook ran
`systemctl reload user@*.service`, whose re-exec of the user manager
broke the scope's bus connection and let the oneshot finish. That also
made systemd-run exit non-zero, so the notifier fell through to the
terminal fallback meant for having no user manager at all, and printed
the migration list into the journal.

Hand the notification to a transient service instead. systemd-run
returns once the unit has started rather than once the payload is done,
so the oneshot completes in milliseconds and nothing ordered after the
target waits on a toast. Put it in background-graphical.slice, which
systemd ships with PartOf=graphical-session.target, so an unanswered
toast ends at logout rather than outliving the session.

The terminal a clicked toast opens is unaffected: uwsm-app re-registers
it into app-graphical.slice, outside this unit's cgroup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 11:19:20 -07:00

64 lines
2.2 KiB
Bash
Executable File

#!/bin/bash
# omarchy:summary=Notify the user when Omarchy has pending migrations
set -euo pipefail
update_in_progress() {
local lock="${XDG_RUNTIME_DIR:-}/omarchy-update.lock"
[[ -n ${XDG_RUNTIME_DIR:-} && -f $lock ]] || return 1
# flock -n only fails here when the lock is held, since the file is ours.
! flock -n "$lock" true 2>/dev/null
}
if update_in_progress; then
exit 0
fi
pending_migrations=$(omarchy-migrate --pending 2>/dev/null) || exit 0
pending_count=$(printf '%s\n' "$pending_migrations" | sed '/^[[:space:]]*$/d' | wc -l)
if (( pending_count == 1 )); then
message="Click to run 1 pending migration."
else
message="Click to run $pending_count pending migrations."
fi
notify_command=$(printf 'if [[ -n $(omarchy-notification-send -u critical -g  "Pending Omarchy Migrations" %q -a) ]]; then omarchy-launch-floating-terminal-with-presentation omarchy-migrate; fi' "$message")
# This runs from omarchy-migrate-notify.service after graphical-session.target,
# but the target can be reached before the shell has claimed
# org.freedesktop.Notifications. Without the wait the toast is sent into the
# void and the user never learns about their pending migrations.
omarchy-notification-wait || true
# That wait is long enough for an update to start underneath us, and the count
# above is already stale by then, so re-check before spending the toast.
if update_in_progress; then
exit 0
fi
# A transient service rather than a scope, which would block here until the
# toast was answered and keep this oneshot activating for that whole time. The
# graphical slice ends an unanswered toast at logout.
unit="omarchy-migrations-notification-$(date +%Y%m%d%H%M%S)"
systemd-run --user --collect --slice=background-graphical.slice --unit="$unit" bash -lc "$notify_command" >/dev/null 2>&1 && exit 0
# Reached when the notification could not be handed off, so fall back to telling
# the user in the terminal.
print_pending_migrations() {
echo "Omarchy has pending migrations. Run omarchy-migrate in a terminal to apply them:"
while IFS= read -r migration; do
[[ -n $migration ]] || continue
printf ' %s\n' "$migration"
done <<<"$pending_migrations"
}
if [[ -t 1 ]]; then
print_pending_migrations
else
print_pending_migrations >&2
fi