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

> Discover how Omarchy's per-user migration system simplifies version upgrades. Learn about timestamped scripts and pending changes for seamless updates.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: internals
- Published: 2026-08-20

---

**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`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/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:

```bash
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`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/tmux-alert-removal-migration-test.sh) validates [`1785189600.sh`](https://github.com/basecamp/omarchy/blob/main/1785189600.sh) behavior:

```bash
#!/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`](https://github.com/basecamp/omarchy/blob/main/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:

```bash

# 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:

```bash
#!/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.