MGS3014: gate superseded
A ci gate stopped before it finished. A later gate started on the same tree and wanted the project locks this one held, so this run yielded them and exited:
[MGS3014] this gate was superseded by a later gate on the same tree (pid 41221, started
2026-09-10T18:04:11Z, magus affected ci --no-default-charms); its verdict would have
described a tree that has since changed; nothing here was wrong.
yielded: project ., along with every other project this run had locked
the later gate is running now, so there is nothing to rerun here
Nothing failed. Nothing in the workspace is wrong. The tree this run was judging moved on while it was judging it, which makes the answer it was about to give an answer to a question nobody is still asking.
The other side of it prints one line and keeps going:
magus: superseded the earlier gate on project . (held by pid 40118 (magus affected ci .),
running 4m12s, in /Users/me/src/acme); its verdict would have described a tree that has
since changed.
Why the later run wins
Before this, the newer gate queued on the workspace lock behind the older one and started only once that finished. That is the right order for two runs doing different work, and the wrong one for two gates on one checkout, where the first gate's compute is spent producing a verdict about files that have already changed. The queue cost the newer run the older one's whole duration, and bought a stale answer.
Ordering is decided by the tree, never by the caller. There is no priority flag and no configuration key, because "which gate matters" is not a question the person typing the command is better placed to answer than the workspace is.
What qualifies
All four hold, or the contention is ordinary (refused immediately, exit 75 - magus never waits on another magus invocation):
- One root. The same resolved workspace root, not merely the same project name. A
sibling worktree serves a project called
.too, and its gate judges different files, so it is refused rather than superseded. - Both gates. The whole
citarget on both sides. Arun buildbehind arun testis refused, a sharded gate is not a gate, and a dry run locks nothing. - Later start wins. The holder began first. A holder with no start time on record is refused rather than superseded, because a supersede that cannot prove it is the newer run is a coin toss.
- Two independent runs. A lock held by one of this run's own ancestors is refused as MGS3007 instead, which is the more specific diagnosis: superseding a parent would kill the run that is blocked waiting for this one to exit.
A run that has finished its targets and is in the settle tail counts as running: it holds the locks, so it is superseded like any other gate.
Superseding is the one contention that is not immediately fail-fast: it asks the holder to stop and waits, briefly (thirty seconds), for it to unwind before falling back to an ordinary refusal. See How the handover works.
How the handover works
The later gate leaves a request beside the lock file naming itself: its pid, its command and when it started. The holder reads it on its own heartbeat, cancels through the same path the stall watchdog uses, and releases. Cancelling alone would not be enough, because a run's locks come back only when it returns, so the abort releases them explicitly and the successor takes the lock within seconds rather than after a timeout.
A marker rather than a signal, for what the abort has to say. A signal carries no
identity, so the stopped run could not name what replaced it, and nothing could tell this
from the Ctrl-C and the supervisor SIGTERM magus already handles as an interrupt.
SIGTERM is not deliverable on Windows at all, and under the server the holder and the
waiter can be threads of one process, where signaling the pid means signaling yourself.
A holder that never answers, because it is stopped, wedged in a syscall, or an older magus that has never heard of a yield request, is not waited on further: the later gate retracts the request after thirty seconds, says so, and refuses like any other contention (exit 75).
Exit status
75 (EX_TEMPFAIL), the same status the contended lock and MGS3010 use.
Nothing here is broken and the same command is valid the moment the later gate is done, so
a harness can tell a yielded gate from a gate that found a bug. Exit 1 would collapse the
two.
Resolution
Nothing. This is not a failure and there is no repair.
-
Read what superseded it. The message names the pid, the command and the start time of the run that took over. That run is judging the tree as it stands now, and its verdict is the one to wait for.
-
Run it again if you actually wanted that verdict. If the superseded run was deliberate, and the tree has stopped changing,
magus affected ciagain. Nothing is cached against a superseded run, so a rerun starts clean. -
Stop starting gates on top of each other. The usual cause is a habit rather than a race: a gate per edit in one checkout.
magus affected cionce, when the change is complete, is the cadence the check is built around. -
Use a worktree if two gates really are independent. A sibling worktree is a different tree, so its gate is never superseded by this one and never supersedes it.
See also
- Concurrency: the workspace lock this decides the order of.
- MGS3007: a lock held by this run's own ancestor, refused rather than superseded.
- MGS3010: a gate deferred before it starts, rather than stopped after it did.
- MGS3012: the stall watchdog, whose abort path this shares.