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

MGS3010: redundant gate deferred

A ci gate was not started. An identical-or-equivalent gate already passed for this branch on this machine, so running another would re-verify what the session store already proves - and magus refuses fast instead. A deferral is never a success: the command exits 75, and the refusal prints every input to its decision, so a reader can reconstruct and dispute it from the message alone:

[MGS3010] not running the ci gate for branch polish: it re-verifies a gate that
  already passed.
  green gate: run inv0193ab, branch polish, commit 2d15f62c, recorded
    2026-09-03T18:04:11Z (14m ago), fingerprint 4c1f22ab9e01
  delta since that gate, every file:
    notes/setup.md: trivial (prose: matches "**/*.md" (built-in default); nothing
      in ci's chain reads it)
    gen/knowledge-graph.json: trivial (generated: generated by .:graph-generate,
      and nothing it reads changed)
  machine pool: saturated: 8 of 8 slots held, 2 runs queued
  override: pass --no-redundancy-check to run it here anyway
  alternative: push; the pull request runs the identical check

This fires when both facts hold at once:

  • A green gate is on record for this branch. Every completed magus run ci and magus affected ci records its branch, commit, input fingerprint and verdict in the per-repository session store, which every worktree shares. The first gate on a branch always runs; so does any gate after a recorded failure - in both cases this feature says nothing at all.
  • The gate would re-verify what that one verified. Either the input fingerprint is identical (derived from the same per-step cache keys a real run mints - sources, tool versions, charms), or everything changed since the green gate's commit tiers trivial (gate sizing): generated output whose generator the change left untouched, and prose (globs: markdown by default, configurable - see below) that no target in ci's chain reads and no package compiles in. A comment-only edit is mechanical, not trivial, because lint and some generators read comments, so it re-gates at that size. A merge commit in the range is never equivalent, however clean - a merge combines two verified histories into a tree neither gate saw, so it always re-gates.

Machine load is not a third condition. It was until 2026-09-07, and that made this refusal unreachable in the case it was written for: the load reading came from the server, ordinary commands ran without a persistent one, so an idle machine always took the advisory path and ran the duplicate. Measured across one session, the advisory printed seven times and about 17 minutes of wall clock went to gates whose answer was already on record. The pool state is still probed and printed - a refusal should say what the machine was doing - but it decides nothing.

The one caller that still only advises is a nested magus, one running under another. It counts its own ancestors' claims as load, so its view of the machine is the reading that cannot be trusted to refuse on.

Exit 75 is EX_TEMPFAIL, the same convention as MGS3009: the refusal is about what this machine has already done, not about the build. The refusal is also recorded in the session store as a deferred gate record pointing at the green gate it deferred to, so the decision stays interrogable after the fact.

Configuring the prose class

magus.project's gate_low_risk key declares the prose globs, following the same project-relative shape as review_required. magus ships **/*.md and **/*.markdown; any declaration replaces the defaults workspace-wide, and [] turns the prose class off. The per-path lines name the matching glob and whether it came from the defaults or a declaration, so each verdict reads straight back to the key that produced it. See workspace. The other classes are deliberately not glob-configurable: generated stays structural and comment-only stays a mechanism, because a glob cannot assert either honestly.

Why

Gates get repeated out of discipline. One person or agent runs magus affected ci after a changelog edit, a regeneration, a clean merge - and again before pushing, and again after a review comment - each run re-proving what the session store already proves. Parallel worktrees multiply it: the record is per-repository and machine-wide precisely so one worktree's green gate is visible to its siblings.

The named alternative is not "skip verification" but "verify where it is not redundant": the push triggers the pull request pipeline, which runs the identical check, on the identical tree, on a machine nobody is waiting at. That is also the answer to the obvious objection - that refusing sends a first red to CI instead of the laptop. It does, deliberately. Redundancy is only declared when the change since the green gate tiers trivial, which is exactly the delta no step of the gate can observe.

Resolution

  1. Push. The pull request runs the same gate; that is the alternative the refusal exists to point at.

  2. Force the run. Pass --no-redundancy-check to magus run ci or magus affected ci to run the full gate: no deferral and no tier reduction.

  3. Dispute a classification. Every path line names the glob or mechanism that decided it. A glob doing the wrong work is yours to change: gate_low_risk in the declaring magusfile.

  4. Change something real. Any change above trivial in the delta makes the next gate run; the check never argues with a tree that differs meaningfully from the one it verified.

See also

  • MGS3009: the machine budget this check reports, and the exit-75 convention.
  • workspace: the gate_low_risk declaration.
  • Concurrency: the scopes magus coordinates on one machine.
  • The broker and the server: the process that owns the budget, and why losing it fails open.
MGS3010cigateredundancyadmissionbrokerworktreesexit 75no-redundancy-checkgate_low_risk
Last updated (20976cb1)
Earlier changes on this page (3)

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.

Magusfile

The magusfile.buzz that declares a project's targets (as export funs) and binds its spells. See targets.

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.

Broker

The per-user background process that holds this host's capacity: the machine budget every run claims slots from, and the shared services runs keep warm. A run starts it on demand; broker: off in magus.yaml runs without one. See server.

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.

Pool

The concurrency pool: the shared set of slots that caps how many targets run in parallel on one machine. Its capacity defaults to MAGUS_CONCURRENCY, then 4 on GitHub-hosted runners, then min(NumCPU, 8); magus status and the dashboard report it live. See server.

Slot

One unit of the pool's capacity. A target acquires the slots it needs to run (most take one) and releases them when it finishes; the pool tracks capacity (total slots), running (acquired), and queued (blocked). See server.

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.

Session

An agent host's conversation, by the id the host delivers to its hooks. magus never mints one: a record with no session is unattributed, and the OS user it carries says whose account ran it.

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.

Advisor

One read-only check from the advice suite: it reads the changeset through magus and writes one titled section of findings. The same advisors run as a pull request comment in CI and inside magus diff --impact locally.

Conventions

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