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

MGS1028: a changed file seeds a project it does not key

A changed file matches no project's declared globs, and still put a project into the affected set - because directory containment seeds, and the root project catches whatever no directory claims.

hint: [MGS1028] projects seeded by changed files nothing declares: . Directory
containment selected them, so the targets they rerun were already correct. Declare the
files in the owning project's sources, or leave them undeclared deliberately (see ...)

It names the projects rather than the files, because magus affected --impact and magus affected --explain - the two commands that emit it - already mark each file inline, and magus describe file <path> explains any one of them in full.

Why this matters

Seeding and keying are separate mechanisms, and this is the case where they disagree.

  • Seeding decides which projects magus affected <target> selects. A changed file seeds the project whose directory contains it, plus every project that declares it from outside that tree; failing both, the root project catches it.
  • Keying decides whether a selected target re-runs or replays. Only declared sources enter a cache key.

An undeclared file gets the first without the second. Touching it selects the project, magus runs the target, the key has not moved, and the answer is the one that was already recorded. The cost is real: a config edit at the root of a monorepo can rebuild and retest everything, every time, and produce nothing new.

The expensive half is the half you can see. The silent half is worse and it is the same declaration missing: when that file genuinely does change what a target produces - a lint rule set, a toolchain pin, a formatter config - the cache does not know, so a target that was going to be selected anyway can still replay a verdict computed under the old rules.

Example

.golangci.yml sits at the workspace root. No project declares it; the root project contains it.

magus\project({
    "spells": [go],   // contributes **/*.go - and nothing else
});

Editing it seeds ., reruns build, test, and lint, and every one of them replays or recomputes an unchanged answer. Meanwhile the lint verdict recorded under the previous rule set stays valid in the cache, because the file that changed the rules was never an input.

Resolve it

If the file is an input, declare it. Declaring is what backs the seeding with a key: the file now matches the owning project's globs, so its rerun can produce a different answer, and this diagnostic clears for it.

magus\project({
    "spells": [go],
    "sources": [".golangci.yml"],
});

Project-wide sources, not a per-target ctx.readsFiles: a target's ctx.readsFiles replaces its footprint rather than adding to it, so declaring a config there would drop the spell's source globs from that target's key.

If the file is not an input, leave it undeclared. A LICENSE, an editor config, a note to yourself - nothing reads them, and declaring one only makes an unrelated edit invalidate a cache. Seeding by containment is the fail-safe working as intended: magus would rather rerun a project than let a gate pass having checked nothing.

Which files, across the whole tree

The diagnostic sees one changeset. magus doctor answers the standing question - every committed file no project declares - as advice, never a failure, because which files are build inputs is your workspace's call and not magus's:

$ magus doctor
[advice] undeclared seeding files: 4 committed file(s) seed a project by directory
         containment while no project declares them

magus describe file <path> explains any single one, and magus affected --impact marks them inline in a changeset.

See also

  • Cache model - what enters a key and what does not
  • Dependencies - how the seed set expands into the affected set
  • MGS1004 - a footprint declaration that never reaches a cache key
  • MGS1010 - the affected set becoming uncomputable, and every project selected instead
  • MGS1018 - a declared output glob that matches nothing
MGS1028magusfilesourcesaffectedcacheseedingdoctor
Last updated (9edeede5)
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.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. See spells.

Buzz

The language magusfiles are written in (the .buzz engine). See engines.

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.

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.

Conventions

Placeholders

Angle brackets mark a value you replace with your own - never type the brackets:

magus run <target>
magus completion <shell>    # e.g. bash, zsh, fish

<target>, <path>, <shell>, <name> and the like are stand-ins, not literal text.