From d35f4b6d597fa531ac1d2eb75fb5804075991426 Mon Sep 17 00:00:00 2001 From: Shrijit Srivastav <62253877+shrijit37@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:29:14 +0530 Subject: [PATCH] Wait on the profile being repaired, not on every browser (#6837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The migration asked a user to close every running browser before repairing the Copy URL shortcut, but a browser only ever rewrites its own Preferences on exit. Waiting on all browsers deadlocks `omarchy update` for anyone whose main browser is effectively never closed: the pending ghosts commonly sit in a stale profile nobody has open, yet the migration blocks on the always-open browser until the prompt is declined, failing the whole update. A running Chromium-family browser holds a SingletonLock (and socket) inside its user-data-dir, so whether the profile being repaired is open is mechanical. Gate on that instead of on the sheer presence of a browser process — the repair proceeds where the affected profile is closed, and browsers attached to other profiles no longer hold the update hostage. The gate stays conservative while an affected profile actually is open, and the existing post-repair verification still catches a browser that starts mid-repair and restores stale Preferences on exit. The migration test now simulates an open profile with its SingletonLock instead of a pgrep stub; every prior scenario still passes. --- migrations/1786643346.sh | 58 ++++++-- .../copy-url-shortcut-migration-test.sh | 139 +++++++++++------- 2 files changed, 128 insertions(+), 69 deletions(-) diff --git a/migrations/1786643346.sh b/migrations/1786643346.sh index 48cdb40b..cc197a91 100644 --- a/migrations/1786643346.sh +++ b/migrations/1786643346.sh @@ -122,10 +122,36 @@ unverified_repairs_exist() { return 1 } -# Only this user's browsers matter — another user's cannot rewrite these -# Preferences. -browsers_running() { - pgrep -x -u "$UID" 'chromium|chrome|brave|msedge|vivaldi-bin|vivaldi|opera|helium' >/dev/null 2>&1 +# A browser only rewrites its own Preferences on exit, so a repair can only be +# reverted by a browser attached to a profile this migration has to touch. +# Whether a profile is open is mechanical: a running Chromium-family browser +# holds a SingletonLock (and socket) inside its user-data-dir. +profile_open() { + # -L catches a SingletonLock left as a dangling symlink; -e covers a plain + # file, and -S the socket — any of them means a browser is attached. + [[ -L $1/SingletonLock || -e $1/SingletonLock || -S $1/SingletonSocket ]] +} + +# Gate on a pending — or to-be-verified — profile actually being open, not on +# browsers in general. Waiting on every browser process deadlocks the update on +# machines whose main browser is effectively never closed: the pending ghosts +# commonly sit in a stale profile nobody has open, yet the update blocks on the +# always-open one. +affected_profile_open() { + local preferences backup profile_root + + for preferences in "${pending[@]}"; do + profile_open "$(dirname "$(dirname "$preferences")")" && return 0 + done + + for profile_root in "${profile_roots[@]}"; do + for backup in "$profile_root"/*/Preferences.omarchy-copy-url-repair.bak; do + [[ -f $backup ]] || continue + profile_open "$(dirname "$(dirname "$backup")")" && return 0 + done + done + + return 1 } find_pending @@ -134,16 +160,16 @@ if (( ! ${#pending[@]} )) && ! unverified_repairs_exist; then fi # A running browser holds Preferences in memory and rewrites the file on exit, -# reverting the repair, so ask for the windows to be closed first. gum draws -# the prompt on stderr, so it must stay attached: suppressing it leaves gum -# waiting for a keypress behind an unpainted screen. Without a terminal to ask -# in, gum fails; then — as on decline — fail so the migration stays pending, -# and the login notifier keeps prompting until a rerun goes through with -# browsers closed. -while browsers_running; do - if ! gum confirm "Close all browser windows to repair the Copy URL shortcut, then continue"; then +# reverting the repair, so ask for the affected windows to be closed first. gum +# draws the prompt on stderr, so it must stay attached: suppressing it leaves +# gum waiting for a keypress behind an unpainted screen. Without a terminal to +# ask in, gum fails; then — as on decline — fail so the migration stays +# pending, and the login notifier keeps prompting until a rerun goes through +# with the affected profiles closed. +while affected_profile_open; do + if ! gum confirm "Close the browser windows to repair the Copy URL shortcut, then continue"; then echo "A running browser would undo the Copy URL shortcut repair." >&2 - echo "Close all browser windows, then run: omarchy-migrate" >&2 + echo "Close the browser windows, then run: omarchy-migrate" >&2 exit 1 fi done @@ -159,9 +185,9 @@ done # A browser that started mid-repair read the stale Preferences and will write # them back on exit; stay pending so the next browser-free run can verify the # repair stuck. -if browsers_running; then +if affected_profile_open; then echo "A browser started during the Copy URL shortcut repair and may undo it on exit." >&2 - echo "Close all browser windows, then run: omarchy-migrate" >&2 + echo "Close the browser windows, then run: omarchy-migrate" >&2 exit 1 fi @@ -172,7 +198,7 @@ fi for preferences in "${pending[@]}"; do if python3 -c "$repair_py" "$preferences" "$pinned_id" check; then echo "A browser undid the Copy URL shortcut repair on exit." >&2 - echo "Close all browser windows, then run: omarchy-migrate" >&2 + echo "Close the browser windows, then run: omarchy-migrate" >&2 exit 1 fi done diff --git a/test/shell.d/copy-url-shortcut-migration-test.sh b/test/shell.d/copy-url-shortcut-migration-test.sh index 2783abeb..f8cf3497 100644 --- a/test/shell.d/copy-url-shortcut-migration-test.sh +++ b/test/shell.d/copy-url-shortcut-migration-test.sh @@ -12,7 +12,8 @@ test_dir=$(mktemp -d) trap 'rm -rf "$test_dir"' EXIT home="$test_dir/home" -preferences="$home/.config/chromium/Default/Preferences" +profile_root="$home/.config/chromium" +preferences="$profile_root/Default/Preferences" mkdir -p "$(dirname "$preferences")" # Any id Chromium once derived from the extension's keyless load path; the @@ -27,23 +28,38 @@ write_stale_preferences() { stub_bin="$test_dir/bin" mkdir -p "$stub_bin" +REAL_PYTHON=$(command -v python3) +export REAL_PYTHON + run_migration() { HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1 } -# A running browser prompts for the windows to be closed; declining (or having -# no terminal to ask in) defers the repair so a rewrite-on-exit cannot revert -# it. -printf '#!/bin/bash\nexit 0\n' >"$stub_bin/pgrep" +# A running Chromium-family browser marks its profile root with a SingletonLock +# symlink to -, a target that never exists on disk. That lock — +# not the mere presence of a browser process — is what the migration waits on. +open_browser() { + mkdir -p "$profile_root" + ln -sfn "test-host-1234" "$profile_root/SingletonLock" +} +close_browser() { + rm -f "$profile_root/SingletonLock" +} + +# The affected profile being open prompts for the windows to be closed; +# declining (or having no terminal to ask in) defers the repair so a +# rewrite-on-exit cannot revert it. printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum" -chmod +x "$stub_bin/pgrep" "$stub_bin/gum" +chmod +x "$stub_bin/gum" write_stale_preferences +open_browser + before_hash=$(sha256sum "$preferences" | cut -d' ' -f1) -run_migration && fail "migration defers while a browser is running" +run_migration && fail "migration defers while the affected profile is open" [[ $(sha256sum "$preferences" | cut -d' ' -f1) == "$before_hash" ]] || - fail "migration leaves preferences alone while a browser is running" -pass "migration defers the repair while a browser is running" + fail "migration leaves preferences alone while the affected profile is open" +pass "migration defers the repair while the affected profile is open" # gum paints its prompt on stderr, so that stream has to stay attached: # suppressing it leaves gum reading keys behind an unpainted screen, which @@ -59,34 +75,47 @@ HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2 grep -q "gum-prompt-painted" "$prompt_stderr" || fail "migration keeps the browser prompt visible" pass "migration keeps the browser prompt visible" -# Confirming the prompt after closing the browser lets the repair proceed. +# A browser holding a different profile root cannot revert this repair, so it +# must not hold the update: the repair goes through without ever reaching the +# prompt, which the still-declining gum stub would otherwise fail. +close_browser +mkdir -p "$home/.config/google-chrome" +ln -sfn "test-host-1234" "$home/.config/google-chrome/SingletonLock" +write_stale_preferences +run_migration || fail "migration repairs while a different profile root is open" +jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Alt+Shift+L"].extension == $pinned' "$preferences" >/dev/null || + fail "migration repairs the shortcut while a different profile root is open" +pass "migration ignores a browser on a different profile root" +rm -f "$home/.config/google-chrome/SingletonLock" "$preferences.omarchy-copy-url-repair.bak" + +# Closing the affected profile and confirming the prompt lets the repair +# proceed. +write_stale_preferences +open_browser cat >"$stub_bin/gum" <<'STUB' #!/bin/bash +"$CLOSE_BROWSER" touch "${GUM_CALLED:?}" exit 0 STUB -cat >"$stub_bin/pgrep" <<'STUB' +cat >"$stub_bin/close-browser" <<'STUB' #!/bin/bash -count_file="${PGREP_COUNT_FILE:?}" -count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 )) -printf '%s\n' "$count" >"$count_file" -(( count == 1 )) && exit 0 || exit 1 +rm -f "$HOME/.config/chromium/SingletonLock" STUB -rm -f "$test_dir/pgrep-count" -GUM_CALLED="$test_dir/gum-called" PGREP_COUNT_FILE="$test_dir/pgrep-count" \ +chmod +x "$stub_bin/gum" "$stub_bin/close-browser" +GUM_CALLED="$test_dir/gum-called" CLOSE_BROWSER="$stub_bin/close-browser" \ HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1 || - fail "migration proceeds once the browser prompt is confirmed" + fail "migration proceeds once the profile is closed and the prompt confirmed" [[ -e $test_dir/gum-called ]] || fail "migration asks before repairing under a running browser" jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Alt+Shift+L"].extension == $pinned' "$preferences" >/dev/null || fail "migration repairs after the browser prompt is confirmed" pass "migration asks to close the browser and repairs on confirmation" rm -f "$preferences.omarchy-copy-url-repair.bak" -printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum" -printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep" -write_stale_preferences -# With browsers closed the ghost registration moves to the pinned id. -printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep" +# With the affected profile closed the ghost registration moves to the pinned id. +printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum" +close_browser +write_stale_preferences run_migration || fail "migration repairs the shortcut when no browser is running" jq -e --arg ghost "$ghost_id" --arg pinned "$pinned_id" ' @@ -99,15 +128,15 @@ jq -e --arg ghost "$ghost_id" --arg pinned "$pinned_id" ' pass "migration rebinds the Copy URL shortcut to the pinned extension id" # A repaired profile has no ghost registration left, so nothing is pending — -# even while a browser is running. +# even while that same profile is open. rm "$preferences.omarchy-copy-url-repair.bak" repaired_hash=$(sha256sum "$preferences" | cut -d' ' -f1) -printf '#!/bin/bash\nexit 0\n' >"$stub_bin/pgrep" +open_browser run_migration || fail "migration reruns cleanly after the repair" [[ $(sha256sum "$preferences" | cut -d' ' -f1) == "$repaired_hash" && ! -e $preferences.omarchy-copy-url-repair.bak ]] || fail "migration is idempotent after the repair" pass "migration is idempotent after the repair" -printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep" +close_browser # A remapped shortcut keeps the user's chosen key while moving to the pinned id. jq -n --arg ghost "$ghost_id" '{extensions: {commands: {"linux:Ctrl+Alt+P": {command_name: "copy-url", extension: $ghost, global: false}}, settings: {}}}' >"$preferences" @@ -127,60 +156,64 @@ jq -e --arg pinned "$pinned_id" ' pass "migration never double-binds the pinned extension" # A browser starting mid-repair may write stale Preferences back on exit, so -# the migration must stay pending for a later browser-free run to verify. +# the migration must stay pending for a later browser-free run to verify. A +# stub hands the repair call through and opens the profile right after it. write_stale_preferences -cat >"$stub_bin/pgrep" <<'STUB' +close_browser +rm -f "$preferences.omarchy-copy-url-repair.bak" +cat >"$stub_bin/python3" <<'STUB' #!/bin/bash -count_file="${PGREP_COUNT_FILE:?}" -count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 )) -printf '%s\n' "$count" >"$count_file" -(( count >= 2 )) && exit 0 || exit 1 +# Called as `python3 -c