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_*_MARKERvariables before invoking migration scripts - Test framework: Creates temporary marker files under
test_tmp/and exports the path - Runtime tools: Use
$XDG_STATE_HOME/omarchyas 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:
- Guard: Exit early if the marker exists
- Execute: Perform the actual work
- 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_*_MARKERenvironment 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →