How to Manage Idempotency Markers in Omarchy: A Complete Guide with Code Examples

Omarchy uses environment variables ending in _MARKER to track completion state, letting migrations and scripts run safely multiple times without causing duplicate changes.

Idempotency markers are the backbone of Omarchy's state management system. They transform destructive shell scripts into safe, repeatable operations by recording whether a specific step has already executed. This article explains how these markers work, where they are defined, and how to implement them correctly in your own Omarchy contributions.

What Are Idempotency Markers?

An idempotency marker is a simple file-based flag that indicates a particular operation has completed successfully. In Omarchy, marker paths are passed through environment variables following the pattern OMARCHY_<NAME>_MARKER. Scripts check for the marker's existence before performing work, and create it afterward using touch.

The convention is enforced across the codebase in migrations/, test/shell.d/, and runtime tools. Markers typically live in $XDG_STATE_HOME/omarchy to keep state separate from configuration.

Where Markers Are Defined and Stored

Marker paths are not hardcoded in the scripts that use them. Instead, they are injected by the execution environment:

  • Migration harness: Sets OMARCHY_*_MARKER variables before invoking migration scripts
  • Test framework: Creates temporary marker files under test_tmp/ and exports the path
  • Runtime tools: Use $XDG_STATE_HOME/omarchy as the default marker directory

This external configuration lets the same script use different marker locations in testing versus production.

Core Usage Pattern: Guard, Execute, Record

Every idempotent script in Omarchy follows a three-step pattern:

  1. Guard: Exit early if the marker exists
  2. Execute: Perform the actual work
  3. Record: Create the marker to prevent re-execution
#!/usr/bin/env bash

# 1. Guard - skip if already done

[[ -f $OMARCHY_EXAMPLE_MIGRATION_MARKER ]] && exit 0

# 2. Execute - perform the migration

echo "Applying one-time configuration..."

# ... actual work here ...

# 3. Record - mark as complete

touch "$OMARCHY_EXAMPLE_MIGRATION_MARKER"

This pattern appears in migrations/1786137597.sh, which contains an explicit comment noting that the original migration is idempotent by design.

Real-World Example: T2 Hardware Repair Marker

The OMARCHY_T2_REPAIR_MARKER demonstrates production marker usage. In test/shell.d/t2-hardware-test.sh, the test suite validates that the T2 repair script respects idempotency:

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

. "$ROOT/bin/omarchy-test-helper"

# Test validates that OMARCHY_T2_REPAIR_MARKER prevents duplicate repairs

marker_path="$test_tmp/t2-repair-marker"
export OMARCHY_T2_REPAIR_MARKER="$marker_path"

# First run should succeed and create marker

run_t2_repair
[[ -f "$marker_path" ]] || fail "Marker was not created"

# Second run should exit cleanly without re-running repair

run_t2_repair && pass "Idempotency respected" || fail "Second run failed"

The test proves the marker system works: subsequent invocations find the existing file and exit immediately.

Common Marker Variables in Omarchy

Variable Purpose Typical Location
OMARCHY_T2_REPAIR_MARKER T2 security chip hardware repair $XDG_STATE_HOME/omarchy/t2-repair
OMARCHY_RETIRED_INSTALLER_ARTIFACTS_MARKER Plymouth theme cleanup $XDG_STATE_HOME/omarchy/retired-artifacts
OMARCHY_UPDATE_LOCK_MARKER Prevents concurrent update runs $XDG_STATE_HOME/omarchy/update.lock

These variables are set by the caller, not the script. This separation of concerns enables flexible testing.

Best Practices for Marker Management

Always Use the Three-Step Pattern

Never omit the guard check. Even "harmless" operations create log noise and waste compute when repeated:


# Correct - exits early

[[ -f $OMARCHY_SOME_MARKER ]] && exit 0
do_work
touch "$OMARCHY_SOME_MARKER"

# Incorrect - reruns every time

do_work  # Missing guard!

touch "$OMARCHY_SOME_MARKER"

Never Delete Production Markers

Markers represent completed state. Deleting them causes the operation to re-run on next invocation, potentially breaking idempotency:


# WRONG - destroys idempotency guarantee

rm -f "$OMARCHY_SOME_MARKER"

# CORRECT - let the marker persist

# (If you need re-run capability, use a different mechanism)

Use Descriptive Naming

The middle portion of OMARCHY_<NAME>_MARKER should clearly describe the operation:


# Good - describes what was done

OMARCHY_PLYMOUTH_THEME_FIX_MARKER
OMARCHY_USER_DIRS_RECREATED_MARKER

# Avoid - vague or implementation-focused names

OMARCHY_STEP1_MARKER
OMARCHY_TEMP_FIX_MARKER

Test Your Markers

Every marker-based script should have a corresponding test in test/shell.d/. The test framework in bin/omarchy-test-helper provides utilities for temporary marker management:

#!/usr/bin/env bash

# test/shell.d/my-migration-test.sh

. "$ROOT/bin/omarchy-test-helper"

marker="$test_tmp/my-migration-marker"
export OMARCHY_MY_MIGRATION_MARKER="$marker"

# Verify marker creation

run_my_migration
assert_file_exists "$marker"

# Verify idempotency

run_my_migration  # Should succeed without error

assert_command_succeeds "Second run failed"

Advanced Pattern: Lock Markers for Mutual Exclusion

Markers can also prevent concurrent execution. The OMARCHY_UPDATE_LOCK_MARKER pattern differs slightly from completion markers:

#!/usr/bin/env bash

# Check for existing lock

if [[ -f $OMARCHY_UPDATE_LOCK_MARKER ]]; then
    echo "Update already in progress" >&2
    exit 1
fi

# Acquire lock (created, not checked)

touch "$OMARCHY_UPDATE_LOCK_MARKER"

# Ensure lock releases even on failure

cleanup() { rm -f "$OMARCHY_UPDATE_LOCK_MARKER"; }
trap cleanup EXIT

# ... perform update ...

Unlike completion markers, lock markers are removed after use. This pattern appears in update-lock tests across the test suite.

Complete Migration Example

Here's a production-ready migration template based on patterns from migrations/1786137597.sh:

#!/usr/bin/env bash

# migrations/1234567890-example.sh

# Idempotent migration: configures example service

set -euo pipefail

# Guard: already applied?

[[ -f $OMARCHY_EXAMPLE_SERVICE_CONFIGURED_MARKER ]] && exit 0

echo "Configuring example service..."

# Create configuration directory

mkdir -p "$HOME/.config/example"

# Write configuration only if missing (additional idempotency layer)

if [[ ! -f "$HOME/.config/example/config.toml" ]]; then
    cat > "$HOME/.config/example/config.toml" << 'EOF'
[service]
enabled = true
EOF
fi

# Record completion

touch "$OMARCHY_EXAMPLE_SERVICE_CONFIGURED_MARKER"
echo "Example service configured successfully"

This script uses dual idempotency: the marker prevents re-entry, and the file existence check prevents overwriting user modifications.

Key Source Files Reference

File Description
migrations/1786137597.sh Migration with explicit idempotency comment
test/shell.d/t2-hardware-test.sh Validates OMARCHY_T2_REPAIR_MARKER behavior
bin/omarchy-test-helper Test utilities for marker management

Consult these files when implementing new marker-based functionality.

Summary

  • Idempotency markers in Omarchy are file paths passed via OMARCHY_*_MARKER environment variables
  • The three-step pattern (guard, execute, record) ensures scripts are safe to re-run
  • Markers are set by callers (migration harness, test framework) not the scripts themselves
  • Never delete completion markers; they represent durable state
  • Lock markers are a variant for mutual exclusion and are cleaned up after use
  • All marker-based code should have corresponding tests in test/shell.d/

Frequently Asked Questions

What happens if I delete an idempotency marker file?

The associated migration or script will run again on next invocation. This may cause duplicate configuration changes, re-apply one-time fixes, or trigger unnecessary restart prompts. Only remove markers during debugging or when intentionally forcing re-execution.

Can I use multiple markers in a single script?

Yes. Complex migrations may use separate markers for distinct phases: OMARCHY_PHASE1_MARKER, OMARCHY_PHASE2_MARKER. This allows partial re-execution if a later phase fails. Check each marker independently and only create markers for successfully completed phases.

How do I test a script that uses environment variables I don't control?

The bin/omarchy-test-helper library sets up isolated test environments. Create a temporary directory, construct your marker path there, and export it before running the script under test. The test framework automatically cleans up temporary files after each test.

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 →