# How the Omarchy Migration System Works: A Complete Guide to Writing Safe Per-User Updates

> Learn how Omarchy's migration system applies per-user updates safely. This guide covers writing new migrations and ensures idempotent execution for your package state.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: how-to-guide
- Published: 2026-08-28

---

**Omarchy's migration system applies one-time repair scripts per-user when package updates need to modify state that pacman cannot manage directly, storing completion markers in `~/.local/state/omarchy/migrations/` to ensure idempotent execution.**

The **Omarchy migration system** provides a robust mechanism for applying stateful configuration changes across user environments when system package updates require modifications beyond what pacman handles natively. Located in the `basecamp/omarchy` repository, this per-user workflow ensures that every user individually runs necessary repair scripts while preventing duplicate executions through marker file tracking.

## Migration Model and Architecture

All migration scripts reside in the `migrations/` directory with the `*.sh` extension. According to the source code in [`agents/skills/migrations.md`](https://github.com/basecamp/omarchy/blob/main/agents/skills/migrations.md), these scripts are invoked by the public command `bin/omarchy-migrate`, which executes automatically during the `omarchy update` process.

The system tracks execution state on a per-user basis. After a migration completes successfully, the migrator creates a marker file at:

```bash
~/.local/state/omarchy/migrations/<migration-filename>

```

This design ensures that every user on the system gets an opportunity to run each migration exactly once, regardless of when they log in or update their environment.

## When Migrations Execute

The **Omarchy migration system** triggers under three specific conditions:

1. **During `omarchy update`** – After package upgrades complete, the update routine automatically runs `omarchy-migrate` followed by any post-update hooks.

2. **At graphical login** – The systemd user service `default/systemd/user/omarchy-migrate-notify.service` checks for pending migrations using `omarchy-migrate --pending`. When pending migrations exist, the service displays a notification that opens a terminal and executes the migrator.

3. **Manually** – Users can invoke `omarchy-migrate` at any time from their terminal; the system automatically skips previously completed migrations based on marker file presence.

## Checking for Pending Migrations

To inspect which migrations are awaiting execution without running them, use the pending flag:

```bash
omarchy-migrate --pending

```

This command lists pending migrations one per line and exits with status `0` when migrations are pending, or a non-zero status otherwise. This interface enables the systemd notification service to determine whether to alert the user at login time.

## How to Write a New Migration

Omarchy provides a scaffolding helper to generate properly formatted migration files. Run the following command from the repository root:

```bash
omarchy-dev-add-migration --no-edit

```

This creates a new file named `migrations/<unix-timestamp>.sh` with the correct permissions and structure.

### Required Migration Structure

Every migration script must adhere to strict formatting rules to ensure safe execution:

- **Permissions:** Set to `0644` (readable by all, not executable)
- **No shebang:** The runner explicitly executes scripts with `bash -euo pipefail`
- **Header:** Begin with an `echo` statement describing the migration's purpose
- **Idempotency:** Every change must first test existing state and exit silently if the work is already done
- **Helper commands:** Use Omarchy helpers such as `omarchy-cmd-present` and `omarchy-pkg-add` for common tasks
- **No shell restarts:** Do not restart the Omarchy shell within migrations; the update flow handles shell reloading automatically after migrations complete

### Minimal Migration Example

The following example from [`agents/skills/migrations.md`](https://github.com/basecamp/omarchy/blob/main/agents/skills/migrations.md) demonstrates a safe, idempotent migration that relinks a Neovim theme:

```bash

# Example migration: Relink Neovim theme to the current Omarchy state

echo "Relink Neovim theme to Omarchy current state"

theme_link="$HOME/.config/nvim/lua/plugins/theme.lua"
target="../../../../.local/state/omarchy/current/theme/neovim.lua"

# If the symlink does not exist, nothing to fix

[[ -L $theme_link ]] || exit 0

# Re-create a correct relative symlink

ln -sfn "$target" "$theme_link"

```

This pattern checks for the existence of the target state before making changes, ensuring the script can run multiple times without error.

### Testing Your Migration

Validate migrations against a temporary home directory to avoid affecting your real user environment:

```bash
HOME=$(mktemp -d) bash -euo pipefail migrations/1781158082.sh

```

To re-run a migration after fixing a bug, remove its marker file and invoke the migrator:

```bash
rm ~/.local/state/omarchy/migrations/1781158082.sh
omarchy-migrate

```

## Summary

- **Omarchy migrations** live in `migrations/*.sh` and execute via `bin/omarchy-migrate` when users run updates or log in graphically.
- The system tracks completion per-user via marker files in `~/.local/state/omarchy/migrations/`, preventing duplicate executions.
- Use `omarchy-dev-add-migration --no-edit` to scaffold new migrations with proper `0644` permissions and no shebang.
- All migrations must be idempotent, testing state before modification and exiting silently when no work is required.
- Test migrations in temporary directories and avoid restarting the Omarchy shell within migration scripts.

## Frequently Asked Questions

### Where does Omarchy store migration completion state?

Omarchy stores per-user completion markers in `~/.local/state/omarchy/migrations/<migration-filename>`. Each user on the system maintains their own independent state, ensuring that migrations run once per user rather than system-wide. The `omarchy-migrate` command checks for these marker files before executing any migration script.

### Can I run a single migration manually without affecting others?

Yes. When you run `omarchy-migrate`, it automatically skips any migrations that have already created their marker files in your home directory. To force a specific migration to re-run, delete its corresponding marker file from `~/.local/state/omarchy/migrations/` and execute `omarchy-migrate` again.

### What permission mode should migration scripts have?

Migration scripts must have permissions set to `0644`. They should not be executable because the `omarchy-migrate` runner explicitly sources them with `bash -euo pipefail` rather than executing them directly. This ensures consistent error handling and pipe failure detection across all migrations.

### How does Omarchy notify users about pending migrations at login?

The systemd user service `omarchy-migrate-notify.service` runs at graphical login and executes `omarchy-migrate --pending`. If the command returns exit code `0` (indicating pending migrations exist), the service displays a desktop notification that opens a terminal and runs the migrator when clicked. This ensures users do not miss critical configuration updates required for their environment to function correctly.