Skip to content

Stamp new migrations with wall-clock time - #13626

Open
Bartok9 wants to merge 2 commits into
omacom:quattrofrom
Bartok9:bartok/dev-add-migration-now
Open

Bartok9 wants to merge 2 commits into
omacom:quattrofrom
Bartok9:bartok/dev-add-migration-now

Conversation

@Bartok9

@Bartok9 Bartok9 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Why

omarchy-dev-add-migration named files after git log -1 --format=%cd --date=unix. Every branch cut from the same commit therefore received the same migration id. The runner records that id as done, so a later migration with the colliding name never runs.

Reported in #13516 (observed collision on 1789095456 from the quattro merge of #11263).

What

Use wall-clock date +%s instead of HEAD's committer date. Claim the path with noclobber / O_EXCL create and bump the id until the create succeeds, so concurrent runs in one checkout cannot mint the same file.

Does not rewrite existing migrations. Merge-time uniqueness against history is still a useful follow-up; this only stops the helper from minting the same id on sibling branches (and tightens same-tree races).

Test

bash test/shell.d/dev-add-migration-test.sh
bash test/shell.d/dev-add-migration-race-test.sh

Note: #13656 targets the same helper lines (with a test). Happy to coordinate / close if maintainers prefer that PR.

Refs #13516

omarchy-dev-add-migration used HEAD's committer date, so every
branch cut from the same commit minted the same id and later
migrations silently never ran. Use date +%s and bump on collision.
@llstrk

llstrk commented Sep 28, 2026

Copy link
Copy Markdown

Automated AI review

Community review: Independent automated community review, unaffiliated with the Omarchy team, intended to help prepare PRs for their review.

Outcome: No defects found in the change itself; the one reproduced gap, concurrent runs in a single checkout, predates this PR and is covered in the optional notes.

Related PR: #13656 makes the same change to the same lines and adds a test, so merging either one makes the other conflict.

Verified
  • Two clones of a commit dated @1789095456, run in different seconds: the base helper gives both 1789095456.sh; this PR gives distinct ids from the current clock.
  • Two checkouts that create a migration in the same second (stubbed clock) still get the same id, since the loop only checks the local tree; the merge-time check the PR defers would cover that.
  • Two sequential runs in the same second create two files (1790633334.sh, then 1790633335.sh); a directory named like the next id is skipped, and a same-numbered file without .sh does not block.
  • A new id is reported as pending by omarchy-migrate --pending, and the largest id on the base branch quattro (1790542069) is below the current clock, so new ids sort after every existing migration.
  • Nothing in omarchy-migrate, omarchy-provision-user or the package build depends on ids being commit times, and agents/skills/migrations.md and docs/file-layout.md still describe the naming accurately.
Optional notes
  1. Optional test improvement: no test covers this helper. The test below fails at the base (e332dc9) and passes at this head.
  2. Concurrent runs in one checkout: the -e check and touch are separate steps, so two simultaneous runs in the same tree print the same path (50 of 50 real-clock trials). This is not a regression (the base always did this), but set -o noclobber with : >"$migration_file" in the loop makes the claim atomic (full block in the reproducer below); that variant passed the race test below and the regression test.
  3. Closes #13516: the issue's second suggestion (a merge-time id check) and its documented hazard remain open: Give the Mise PATH cleanup a collision-free migration id #12174 renames the Mise repair off 1789095456.sh while Recreate lock screen fingerprint PAM file for pre-quattro setups #11322 still adds a new 1789095456.sh. Refs #13516 would keep that tracked.
Reproducer: regression test (fails at base, passes at 3ffd965)

Proposed regression test (verification, not a defect). Save as test/shell.d/dev-add-migration-test.sh and run bash test/shell.d/dev-add-migration-test.sh from the repository root.

#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

# A checkout whose HEAD was committed at 1789095456, the id #13516 saw reused.
checkout="$test_tmp/omarchy"
mkdir -p "$checkout/migrations"
git -C "$checkout" init -q
GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=test@example.invalid \
GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=test@example.invalid \
GIT_AUTHOR_DATE=@1789095456 GIT_COMMITTER_DATE=@1789095456 \
  git -C "$checkout" commit --allow-empty -q -m base

# Pin the clock so the expected ids are exact.
stub_bin="$test_tmp/bin"
mkdir -p "$stub_bin"
cat >"$stub_bin/date" <<'SH'
#!/bin/bash
[[ ${1:-} == "+%s" ]] || exit 1
echo 1790700000
SH
chmod +x "$stub_bin/date"

add_migration() {
  PATH="$stub_bin:$PATH" OMARCHY_PATH="$checkout" "$ROOT/bin/omarchy-dev-add-migration" --no-edit
}

created=$(add_migration)
[[ $created == "$checkout/migrations/1790700000.sh" && -f $created ]] ||
  fail "a new migration is named after the current time, not HEAD's commit time" "created: $created"
pass "a new migration is named after the current time, not HEAD's commit time"

created=$(add_migration)
[[ $created == "$checkout/migrations/1790700001.sh" && -f $created ]] ||
  fail "a second migration in the same second takes the next free id" "created: $created"
pass "a second migration in the same second takes the next free id"

Output at the base e332dc9 (exit status 1):

created: /tmp/tmp.liNt7snZIh/omarchy/migrations/1789095456.sh
not ok - a new migration is named after the current time, not HEAD's commit time

Output at 3ffd965 (exit status 0):

ok - a new migration is named after the current time, not HEAD's commit time
ok - a second migration in the same second takes the next free id
Reproducer: concurrent runs (fails at 3ffd965)

Save as test/shell.d/dev-add-migration-race-test.sh and run bash test/shell.d/dev-add-migration-race-test.sh from the repository root.

#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

# Pin the clock so both runs start from the same second.
stub_bin="$test_tmp/bin"
mkdir -p "$stub_bin"
cat >"$stub_bin/date" <<'SH'
#!/bin/bash
[[ ${1:-} == "+%s" ]] || exit 1
echo 1790700000
SH
chmod +x "$stub_bin/date"

for trial in $(seq 1 20); do
  checkout="$test_tmp/omarchy-$trial"
  mkdir -p "$checkout/migrations"

  PATH="$stub_bin:$PATH" OMARCHY_PATH="$checkout" "$ROOT/bin/omarchy-dev-add-migration" --no-edit >"$test_tmp/$trial.a" &
  first=$!
  PATH="$stub_bin:$PATH" OMARCHY_PATH="$checkout" "$ROOT/bin/omarchy-dev-add-migration" --no-edit >"$test_tmp/$trial.b" &
  second=$!
  wait "$first" "$second"

  [[ $(<"$test_tmp/$trial.a") != "$(<"$test_tmp/$trial.b")" ]] ||
    fail "two concurrent runs in one checkout create distinct migrations" "trial $trial: both printed $(<"$test_tmp/$trial.a")"
done
pass "two concurrent runs in one checkout create distinct migrations"

Output at 3ffd965 (exit status 1):

trial 1: both printed /tmp/tmp.lvPbZ8NP34/omarchy-1/migrations/1790700000.sh
not ok - two concurrent runs in one checkout create distinct migrations

Without the stubbed clock, 50 of 50 trials of two concurrent runs in one checkout printed the same path.

With the loop and touch replaced by the noclobber block below, the same test prints ok - two concurrent runs in one checkout create distinct migrations (exit status 0), and the regression test above still passes.

id=$(date +%s)
migration_file="migrations/${id}.sh"
# noclobber creates the file with O_EXCL, so concurrent runs cannot claim one id.
set -o noclobber
until { : >"$migration_file"; } 2>/dev/null; do
  [[ -e $migration_file ]] || { echo "Cannot create $PWD/$migration_file" >&2; exit 1; }
  id=$((id + 1))
  migration_file="migrations/${id}.sh"
done

Review information

Test scope: Source review and sandboxed command runs with --no-edit on disposable copies, at 3ffd965; the same-second cross-machine case was modelled with a stubbed clock only.

AI process: Opus 5.5 Medium coordination and synthesis, Opus 5.5 Xhigh technical review and final fact check, GPT 6 Sol Xhigh search for related issues, Opus 5.5 Medium editorial check.

Opt out: To stop receiving these reviews, reply to this comment saying so.

Use noclobber O_EXCL create so concurrent helper runs in one tree
cannot mint the same path. Cover wall-clock naming and the race.
@Bartok9

Bartok9 commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough community review — agreed on the findings.

Pushed a follow-up that:

  • claims the migration path with set -o noclobber / O_EXCL create (addresses the same-tree concurrent race; not a regression from base, but worth fixing while we’re here)
  • adds test/shell.d/dev-add-migration-test.sh and test/shell.d/dev-add-migration-race-test.sh (both green locally)
  • switches the trailer to Refs #13516 so the merge-time uniqueness follow-up stays tracked

Also noted the overlap with #13656 — fine to take either; this branch now includes the race-safe claim + tests if that’s useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants