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 ...)
Three commands emit it, and they differ in one detail. magus affected --impact and
magus affected --explain name the projects only: both already mark each file inline,
and magus describe file <path> explains any one of them in full. A plain
magus affected <target> run, the one that actually pays for the rerun, names each
project's files too, because it prints a project list and never the changeset:
hint: [MGS1028] projects seeded by changed files nothing declares: . (.golangci.yml,
dprint.json). Directory containment selected them, so the targets they rerun were
already correct. ...
That line reports the projects a run selected through undeclared files alone. A
project with one declared changed file among them had a keyed reason to run, and is not
what this reports; --impact and --explain still mark its undeclared files.
It survives -s/--silent, like every other hint, and MAGUS_HINTS_ENABLED=false (or
hints.enabled: false) drops it. Nothing is skipped either way: the run proceeds exactly
as it would have, and the line is context rather than a verdict.
Reading it as data
magus affected <target> -o json carries the same set as undeclared_seeds, a list of
project paths, absent when there are none. -o jsonl carries one run.diagnostic event
per project, with "code": "MGS1028" and the project path as its unit, so a consumer
counts the code rather than matching the hint's wording.
A ci gate also records the set on its gate result in the per-repository session store,
beside the projects it covered, so the debt is countable across a branch rather than only
visible in the run that printed it. When a later gate defers to that one
(MGS3010), the refusal names it.
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