[6.x] Prevent overlapping git commit jobs#14672
Open
aerni wants to merge 2 commits into
Open
Conversation
added 2 commits
May 14, 2026 12:02
Mark CommitJob as ShouldBeUnique so concurrent dispatches collapse to a single queued job. Without this, multiple workers running CommitJobs against the same repo race on `git add` (producing `index.lock: File exists` errors) and on `git push` (producing non-fast-forward rejections because each worker's push uses a stale view of origin). The unique lock TTL is 120s, comfortably outlasting the default queue worker timeout so a hard worker crash recovers within 2 minutes.
When multiple saves are dispatched within a single CommitJob's lock window, ShouldBeUnique drops the duplicate dispatches and the queued job's `git add` sweeps up everyone's changes — but the queued job still carries the first dispatcher's user. Attributing a multi-author commit to a single user is misleading. Track dispatch attempts in cache via Cache::increment in dispatchCommit, then in CommitJob::handle null out the committer when the pull reveals more than one dispatch occurred. Git's existing fallback then uses the configured user.name / user.email (the bot account) — consistent with how scheduler-driven saves are already attributed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #11322
Issue
Concurrent
CommitJobs running against the same.git/directory cause two related failures:fatal: Unable to create '.git/index.lock': File exists.(twogit add/commitprocesses racing on git's index mutex)error: failed to push some refs ... non-fast-forward(each worker pushing with a stale view of origin, so the second push fails git's atomic ref CAS)Both root-cause to the same thing: multiple queue workers running
CommitJobs concurrently against one repo.Solution
Mark
CommitJobasShouldBeUniqueso duplicate dispatches are dropped at the dispatch layer. The first dispatch wins; itsgit add {{ paths }}sweeps up any changes from saves that landed during the lock window. No two git processes fromCommitJobever run simultaneously.uniqueForis set to 120 seconds. In normal operation the lock is released the momenthandle()returns;uniqueForis only the crash-safety net for SIGKILL / OOM scenarios where Laravel's release-on-completion code never runs. 120s comfortably outlasts the default Laravel queue workertimeout(60s), so a hard crash recovers within 2 minutes.Attribution
Coalescing changes the attribution story: a burst of saves now produces one commit, and naively that commit gets attributed to whichever user happened to dispatch first. Other users' changes are in the commit but their name isn't on it.
The second commit in this PR addresses that with a small counter pattern:
Git::dispatchCommit()callsCache::increment('statamic-git-pending-saves')on every attempt (including dispatches that will be dropped).CommitJob::handle()doesCache::pull(...)at the start. If the count is greater than 1, the committer is replaced withnull.Git's existing fallback ingitUserName()/gitUserEmail()then attributes the commit to the configureduser.name/user.email(the bot account).This is the same fallback Statamic already uses today for scheduler-driven and CLI-driven saves where there's no authenticated user, so we're extending an existing pattern, not introducing a new one.