How Omarchy's Per-User Migration System Handles Version Upgrades

Omarchy handles version upgrades by running timestamped, incremental migration scripts through a dedicated omarchy-migrate command that tracks execution state and applies only pending changes per user.

When upgrading from one major version of Omarchy to another, user configurations and runtime state must evolve safely. The system in basecamp/omarchy solves this with a formal per-user migration system that applies ordered, idempotent transformations without requiring manual intervention. This article breaks down the architecture, key source files, and safety mechanisms that make Omarchy upgrades reliable.

Timestamped Migration Scripts Establish Total Ordering

All migrations live in the migrations/ directory and follow a strict naming convention: Unix timestamp plus .sh extension. For example, migrations/1785189600.sh represents a specific point in time.

This design guarantees chronological execution order. Since Unix timestamps increase monotonically, lexicographic sorting produces the correct sequence. The system never needs complex dependency management—earlier migrations naturally precede later ones.

The 1785189600.sh migration demonstrates a typical transformation: it removes legacy tmux alert hooks and cleans up the Omarchy widget configuration. Each script is a standalone Bash file that can be inspected, tested, and reviewed independently.

The omarchy-migrate Command: Core Discovery and Execution

The bin/omarchy-migrate script serves as the migration engine. Its responsibilities include:

  • Scanning migrations/ for scripts newer than the last-run marker
  • Executing pending scripts with bash -euo pipefail for strict error handling
  • Updating ~/.local/share/omarchy/migration-state after each successful run

The strict mode flags (-e exit on error, -u treat unset variables as errors, -o pipefail propagate pipeline failures) ensure that any migration failure halts execution. This prevents partial state corruption.

The state marker prevents re-execution. Once 1785189600.sh completes successfully, its timestamp is recorded. Subsequent runs skip it automatically.

Integration with the Upgrade Flow

The upgrade script bin/omarchy-upgrade-to-quattro orchestrates the full version transition. After installing new packages, it invokes migrations via the run_post_upgrade_migrations function:

run_post_upgrade_migrations() {
    PATH="$package_path" command -v omarchy-migrate >/dev/null 2>&1 || return 0
    log "Running Omarchy migrations"
    if ! run_as_user_omarchy OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1 omarchy-migrate; then
        warn "Could not run Omarchy migrations; the user may be prompted to run them after login."
    fi
}

Key integration points visible in this source:

  • Conditional execution: Returns silently if omarchy-migrate is unavailable
  • User context switching: run_as_user_omarchy ensures migrations run as the target user, not root
  • Environment signaling: OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1 informs the migration system that modifying live state is safe
  • Graceful degradation: Warns but continues if migrations cannot run immediately

Idempotent Design Patterns in Migration Scripts

Each migration script is engineered for safe re-execution. Common patterns found in migrations/1785189600.sh and similar files include:

  • Existence checks before file modification: [[ -f "$file" ]] guards against operating on missing paths
  • Content comparison with cmp -s to avoid unnecessary writes
  • Surgical removal of only Omarchy-specific entries, preserving user customizations

The tmux migration specifically:

  1. Edits ~/.config/tmux/tmux.conf only when the file exists
  2. Uses awk to preserve non-Omarchy configuration lines
  3. Targets only omarchy-tmux-alert hook entries in the running tmux server

This precision ensures that re-running the migration produces no changes after the first successful application.

Testing Infrastructure for Migration Correctness

Every migration ships with dedicated tests under test/shell.d/. The harness:

  • Sets up isolated, temporary user environments
  • Executes migrations directly with the same strict flags (bash -euo pipefail)
  • Asserts expected file system changes
  • Verifies idempotency by running the migration twice

The test/shell.d/tmux-alert-removal-migration-test.sh validates 1785189600.sh behavior:

#!/usr/bin/env bash
migration=$(grep -rl 'Remove the tmux alert hooks' "$ROOT/migrations" | head -n1)
HOME="$test_home" bash "$migration" >/dev/null

# Verify that the tmux config no longer contains Omarchy hook lines

grep -q 'omarchy-tmux-alert' "$test_home/.config/tmux/tmux.conf" && fail "hook still present"
pass "tmux hooks removed"

The test/shell.d/upgrade-to-quattro-test.sh file verifies end-to-end integration: that post-upgrade migrations trigger correctly during version jumps.

Manual Migration Operations

While the upgrade flow runs migrations automatically, administrators can invoke omarchy-migrate directly for debugging or forced re-application:


# List pending migrations without executing

omarchy-migrate --list

# Apply all pending migrations

omarchy-migrate

# Run a specific migration by timestamp

omarchy-migrate --run 1785189600

Migration Script Skeleton

New migrations follow this structure:

#!/usr/bin/env bash
set -euo pipefail

# Example: remove a legacy config key

legacy_conf="$HOME/.config/example.conf"
if [[ -f $legacy_conf ]]; then
    tmp=$(mktemp)
    grep -v '^legacy_key=' "$legacy_conf" >"$tmp"
    mv "$tmp" "$legacy_conf"
fi

The set -euo pipefail header is mandatory per the project's strict error-handling standards.

Summary

  • Timestamped filenames in migrations/ provide automatic chronological ordering without dependency management
  • bin/omarchy-migrate handles discovery, execution, and state tracking via ~/.local/share/omarchy/migration-state
  • bin/omarchy-upgrade-to-quattro integrates migrations into the upgrade flow with user context switching and environment signaling
  • Strict execution mode (bash -euo pipefail) and idempotent design patterns prevent partial state corruption
  • Test coverage under test/shell.d/ validates each migration's correctness and re-runnability

Frequently Asked Questions

How does Omarchy prevent the same migration from running twice?

The migration system writes a state marker to ~/.local/share/omarchy/migration-state after each successful script execution. On subsequent runs, omarchy-migrate compares migration timestamps against this marker and skips any that have already been applied. This state is stored per-user, so different users on the same system migrate independently.

What happens if a migration script fails?

The bash -euo pipefail strict mode causes immediate termination on any error. The run_post_upgrade_migrations wrapper in bin/omarchy-upgrade-to-quattro catches this, logs a warning, and continues the upgrade process. The failed migration remains unmarked, so it will be retried on the next invocation—either automatically during a future upgrade or manually by the user.

Can migrations be run outside of the automatic upgrade flow?

Yes. The omarchy-migrate command supports manual invocation with flags like --list and --run <timestamp>. Administrators can also execute individual migration scripts directly with bash, though this bypasses state tracking and should be used only for debugging or recovery scenarios.

Why use Unix timestamps instead of sequential numbers for migration names?

Unix timestamps provide conflict-free distributed creation. Multiple developers can write migrations concurrently without coordinating to avoid sequence number collisions. Timestamps also encode when each migration was authored, which aids debugging and audit trails. The lexicographic sort of timestamps matches chronological order, so no complex parsing is required.

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 →