How Omarchy Migrations Run Per‑User: Complete Technical Guide

Omarchy migrations run per‑user by storing completion markers in each user's home directory (~/.local/state/omarchy/migrations) and executing scripts under the invoking user's UID, allowing granular privilege escalation only when needed.

When you upgrade Omarchy, the system must apply one‑time fixes, policy updates, and configuration changes without requiring manual intervention. The migration system achieves this by running scripts as the user who invokes the upgrade, tracking completion state locally, and deferring privileged operations to explicit sudo calls within each script.

How the Per‑User Migration System Works

The omarchy-migrate command in bin/omarchy-migrate implements a stateful, user‑scoped execution model. Rather than applying migrations system‑wide, it treats each user as an independent migration target.

Discovery and State Tracking

omarchy-migrate scans $OMARCHY_PATH/migrations/*.sh to find all available migration scripts. It compares these against completion markers stored in:

"$HOME/.local/state/omarchy/migrations/"

Each marker file matches its corresponding script name exactly (e.g., 1788619462.sh). This design ensures migrations run once per user regardless of how many times the command executes.

Execution Model

Scripts execute with bash -euo pipefail, enforcing strict error handling. The runner sources each pending script under the current user's UID, meaning:

  • Unprivileged operations proceed without elevation
  • Privileged operations require explicit sudo calls within the script
  • Failure handling aborts immediately on non‑zero exit, leaving the migration unmarked for retry

This contrasts with system‑wide migration systems that typically require root throughout or complex privilege separation logic.

Notification Integration

After processing completes—whether successfully or with pending migrations remaining—omarchy-migrate triggers omarchy-migrate-notify.service. This systemd user service displays a desktop alert such as "Pending Omarchy Migrations" defined in default/systemd/user/omarchy-migrate-notify.service.

Running Migrations Manually

Apply All Pending Migrations

$ omarchy-migrate

This discovers unmarked scripts, executes them sequentially, and writes completion markers for successful runs.

Preview Pending Migrations

$ omarchy-migrate --pending

# → 1788619462.sh

# → 1788596255.sh

This lists scripts awaiting execution without modifying state.

Debug a Specific Migration


# Execute directly (omarchy-migrate manages markers on successful completion)

$ bash "$OMARCHY_PATH/migrations/1788619462.sh"

Direct execution bypasses the runner's state management—you must handle markers manually in this mode.

Key Files in the Migration System

Path Purpose
bin/omarchy-migrate Main discovery, execution, and state management runner
migrations/*.sh Individual migration scripts (timestamped filenames like 1788619462.sh)
default/systemd/user/omarchy-migrate-notify.service User service for desktop notifications post-migration
test/shell.d/migrate-scope-test.sh Validates per‑user isolation and state separation
test/shell.d/migrate-wrapper-test.sh Tests runner functionality: pending listings, notifications, exit codes
docs/update-process.md Documents migration integration within upgrade flows

Integration with Upgrade Commands

Migration execution occurs automatically as a final step in upgrade workflows:

  • omarchy-upgrade-to-quattro
  • omarchy-update
  • Other package management wrappers

This sequencing ensures new code is present before migrations attempt to modify configuration or apply fixes that depend on updated assets.

Per‑User Design Benefits

The state directory approach in ~/.local/state/omarchy/migrations/ provides concrete protections:

  • Isolation: Each user's migration progress is independent; one user's failure or delay doesn't block others
  • Retry safety: Failed migrations remain unmarked, enabling partial recovery without re‑executing successes
  • Least privilege: Scripts run unprivileged by default, escalating only for operations explicitly requiring sudo

The test suite in test/shell.d/migrate-scope-test.sh verifies this isolation by confirming that completion markers from one user don't satisfy migration requirements for another.

Summary

  • Omarchy migrations run per‑user via state markers in ~/.local/state/omarchy/migrations/
  • omarchy-migrate discovers, executes, and records migrations from migrations/*.sh
  • Execution uses bash -euo pipefail under the invoking user's UID with explicit privilege escalation
  • Failed migrations retry automatically on next invocation; successes never re‑execute
  • Desktop notifications trigger via omarchy-migrate-notify.service after each run
  • Upgrade commands call migrations automatically as their final step

Frequently Asked Questions

Where does Omarchy store per‑user migration state?

Omarchy writes completion markers to "$HOME/.local/state/omarchy/migrations/" as files matching their corresponding script names. This user‑writable directory ensures cross‑user isolation without requiring system‑level coordination.

What happens if a migration script fails?

The runner aborts immediately on non‑zero exit due to set -e. No marker file is written, so the next omarchy-migrate invocation will retry that specific script while skipping already‑successful migrations.

Can I run migrations for another user?

No—omarchy-migrate operates exclusively under the current user's UID and writes state to that user's home directory. Administrators must either run migrations as each target user or design system‑level automation that invokes the command within user contexts.

How do I know if migrations are pending after an upgrade?

Run omarchy-migrate --pending to list unexecuted scripts, or check for the desktop notification triggered by omarchy-migrate-notify.service. The notification appears automatically when the upgrade process invokes omarchy-migrate at completion.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →