magus v0.4.3 is out. See what's new
¶ View markdown source · ✎ Suggest an edit
4 min read

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 ci target on both sides. A run build behind a run test is 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.

  1. 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.

  2. Run it again if you actually wanted that verdict. If the superseded run was deliberate, and the tree has stopped changing, magus affected ci again. Nothing is cached against a superseded run, so a rerun starts clean.

  3. 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 ci once, when the change is complete, is the cadence the check is built around.

  4. 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.
MGS3014cigatesupersedelocksworkspace-lockworktreesexit 75concurrency
Last updated (95680f58)
Earlier changes on this page (2)

Full history ↗ · Blame source ↗

Glossary

Workspace

The magus root directory that owns a set of projects and shared config; the unit magus operates over. See workspace.

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. See workspace.

Target

A named operation (build, test, ...) you invoke with magus run <target>; it may compose a spell's tool-native operations and depend on other targets. See targets.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). See operations.

Charm

An execution modifier attached with : (lint:rw) that changes how a target runs, not which one; the built-in rw flips a check-only target to mutate in place, and ci always strips it. See charms.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

Affected

The set of projects touched by a change; magus affected <target> runs a target only over them. See affected.

Server

The background process a person starts with magus server start. It serves MCP, the console, background jobs and the warm knowledge graph, and adopts nested magus calls into one pool. See server.

CI

An ordinary magusfile-defined target you compose yourself with magus\needs - magus does not hardcode its stages. Magus.RunCI treats it specially only in that it strips the rw charm, it is the anchor magus affected ci keys off, and a selected scope with no project declaring it is a load error rather than a silent no-op. See targets.

Concurrency

How many targets run at once. It is bounded by the pool's capacity and set with --concurrency, MAGUS_CONCURRENCY, or the concurrency config key. See server.

Queued

A target that wants a slot while the pool is full; it blocks first-in-first-out until a slot frees. The dashboard colors a sample with queued > 0 accordingly. See server.

Invocation

One magus process's recorded facts - the targets it finished, their outcomes, the lease it acted as, and the session it ran in when a host delivered one - kept in a repo-scoped store every worktree shares. magus session lists them; the store prunes itself by last-fact age.

Window

The terminal a command runs in. It keys fire-once notices for a caller no host gave a session, and is never recorded as a session.

Run

One target executing under one magus invocation, such as magus run test web or magus affected ci. A run keeps its captured output behind an output reference. Every magus run is a run whether or not any job asked for it; see Job for how the two relate.

Lease

The grant a holder takes on a job: the write and read paths that job declared, enforced in the checkout that took it with magus job exec. A job is the piece of work; a lease is permission over it.

Conventions

This page uses none of the site's convention markers. The full set is on the conventions page.