Running omarchy update gets stuck on this migration and never advances, even after closing every browser window and repeatedly answering Yes:
Running migration (1786643346)
Repair the Copy URL shortcut for profiles that predate its pinned extension id
Close the browser windows to repair the Copy URL shortcut, then continue
Yes No
Pressing Yes / Enter just redraws the same prompt. No error message. No browser windows appear to be open — pgrep -fl chromium, pgrep -fl chrome, pgrep -fl brave etc. all come back empty.
The migration script (/home/<user>/.local/share/omarchy/migrations/1786643346.sh) does not check for running browser processes. It checks whether a SingletonLock file (or SingletonSocket) exists inside each Chromium-family profile directory — that's the mechanism Chromium itself uses to mark "a browser instance is attached to this profile."
If Chromium (or Brave, Edge, Vivaldi, Opera, etc.) didn't exit cleanly at some point in the past — a crash, a kill -9, a power loss, an OOM kill — that lock file can be left behind on disk even though no process is running anymore. The migration then waits forever for a "browser" that was never actually open in the current session.
This is a "stale lock", not a real conflict. The fix is to remove the leftover lock files (after confirming, with lsof/fuser, that no live process actually holds them), then re-run the migration.
-
Confirm no relevant browser process is genuinely running:
pgrep -fl 'chromium|chrome|brave|microsoft-edge|vivaldi|opera|helium'If this returns nothing, no browser is open.
-
Look for
Singleton*files in every profile root the migration checks:for root in \ "$HOME/.config/chromium" \ "$HOME/.config/google-chrome" \ "$HOME/.config/google-chrome-beta" \ "$HOME/.config/google-chrome-unstable" \ "$HOME/.config/BraveSoftware/Brave-Browser" \ "$HOME/.config/BraveSoftware/Brave-Browser-Beta" \ "$HOME/.config/BraveSoftware/Brave-Browser-Nightly" \ "$HOME/.config/microsoft-edge" \ "$HOME/.config/microsoft-edge-beta" \ "$HOME/.config/microsoft-edge-dev" \ "$HOME/.config/vivaldi" \ "$HOME/.config/opera" \ "$HOME/.config/helium" do for f in "$root/SingletonLock" "$root/SingletonSocket" "$root/SingletonCookie"; do [ -e "$f" ] || [ -L "$f" ] && echo "$f" done done
Note: don't just
grepfor the word "chrom" when hunting for these — that silently excludes Brave, Edge, Vivaldi, and Opera profile directories, none of which contain "chrom" in their path. -
For anything found, confirm it's actually stale (not held by a live process) instead of assuming:
lsof -- "$HOME/.config/chromium/SingletonLock"Empty output = stale, safe to remove. Any output = a real process still holds it — close that browser normally instead of deleting the file.
-
Also check for a leftover repair marker, which independently keeps the migration gated even after the lock is gone:
find "$HOME/.config" -iname 'Preferences.omarchy-copy-url-repair.bak' 2>/dev/null
Once you've confirmed (via lsof) that a lock is stale and not held by any real process:
rm -f "$HOME/.config/chromium/SingletonLock"
# repeat for any other profile roots that showed a stale lock, e.g.:
# rm -f "$HOME/.config/BraveSoftware/Brave-Browser/SingletonLock"
# and remove any leftover repair backup markers found above:
# rm -f "$HOME/.config/chromium/Default/Preferences.omarchy-copy-url-repair.bak"Then re-run:
omarchy-migrate
# or
omarchy updateThe migration should now complete immediately, since profile_open() in the migration script no longer finds a lock file and exits its wait loop.
#!/usr/bin/env bash
set -uo pipefail
profile_roots=(
"$HOME/.config/chromium"
"$HOME/.config/google-chrome"
"$HOME/.config/google-chrome-beta"
"$HOME/.config/google-chrome-unstable"
"$HOME/.config/BraveSoftware/Brave-Browser"
"$HOME/.config/BraveSoftware/Brave-Browser-Beta"
"$HOME/.config/BraveSoftware/Brave-Browser-Nightly"
"$HOME/.config/microsoft-edge"
"$HOME/.config/microsoft-edge-beta"
"$HOME/.config/microsoft-edge-dev"
"$HOME/.config/vivaldi"
"$HOME/.config/opera"
"$HOME/.config/helium"
)
found_locks=()
found_backups=()
for root in "${profile_roots[@]}"; do
[ -d "$root" ] || continue
for f in "$root/SingletonLock" "$root/SingletonSocket" "$root/SingletonCookie"; do
{ [ -e "$f" ] || [ -L "$f" ]; } && found_locks+=("$f")
done
for bak in "$root"/*/Preferences.omarchy-copy-url-repair.bak; do
[ -f "$bak" ] && found_backups+=("$bak")
done
done
stale_locks=()
live_locks=()
for f in "${found_locks[@]}"; do
if command -v lsof >/dev/null 2>&1 && lsof -- "$f" >/dev/null 2>&1; then
live_locks+=("$f")
else
stale_locks+=("$f")
fi
done
echo "Stale locks: ${#stale_locks[@]}"
printf ' %s\n' "${stale_locks[@]}"
echo "Live locks (do NOT remove - close that browser instead): ${#live_locks[@]}"
printf ' %s\n' "${live_locks[@]}"
echo "Leftover repair backups: ${#found_backups[@]}"
printf ' %s\n' "${found_backups[@]}"
if [ ${#live_locks[@]} -eq 0 ] && { [ ${#stale_locks[@]} -gt 0 ] || [ ${#found_backups[@]} -gt 0 ]; }; then
read -r -p "Remove stale lock(s)/backup(s) above? [y/N] " ans
if [[ "$ans" =~ ^[Yy] ]]; then
for f in "${stale_locks[@]}" "${found_backups[@]}"; do rm -fv "$f"; done
echo "Done. Now run: omarchy-migrate"
fi
fiThe design intent (visible in the migration script's own comments) is reasonable: waiting on every browser process in general would deadlock updates on machines where a browser is effectively always open, so the script narrows the check to only the specific profiles that actually need repairing. The trade-off is that it relies on SingletonLock as a proxy for "is this profile currently open" — which is accurate for a live browser, but can go stale if a prior session didn't shut down cleanly.
Tested on Omarchy (Arch-based), migration ID 1786643346. If you hit this on a different migration ID with the same "browser windows" wording, the same stale-lock diagnosis likely still applies — just adjust the script path accordingly.