Skip to content

Instantly share code, notes, and snippets.

@daltondiaz
Created August 19, 2026 12:20
Show Gist options
  • Select an option

  • Save daltondiaz/66d84e401faf2ff058421c3aa539db02 to your computer and use it in GitHub Desktop.

Select an option

Save daltondiaz/66d84e401faf2ff058421c3aa539db02 to your computer and use it in GitHub Desktop.
Fix: Omarchy migration ("Repair the Copy URL shortcut") loops forever

Fix: Omarchy migration ("Repair the Copy URL shortcut") loops forever

Symptom

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.

Root cause

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.

How to check

  1. 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.

  2. 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 grep for 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.

  3. 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.

  4. 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

The fix

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 update

The migration should now complete immediately, since profile_open() in the migration script no longer finds a lock file and exits its wait loop.

All-in-one script

#!/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
fi

Why this happens

The 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.

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