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

MGS1035: a target writes the tree when it was given no rw charm

A target reads ctx.hasCharm("rw") and still calls fs\writeFile outside that branch:

[fail] writes-without-rw-charm: 1 fs\writeFile call(s) sit outside the target's own rw
branch, so a run given no rw charm edits the tree it was asked to judge and then reports
its own edit
    buzz_test writes the tree outside its rw branch (magusfile.buzz:2866)

Why it matters

Branching on rw means the target runs two ways. The run WITHOUT the charm is a verdict: it looks at the tree and says whether it is settled. That is the whole premise of magus affected ci --no-default-charms as a drift gate, and of a check you can run on a tree you do not want modified.

A write outside the branch breaks the premise in the order that hides it:

  1. The target renders the content it expects.
  2. It writes that content to the file.
  3. It compares the file to what it expected, and they now differ from what was committed.
  4. It reports the difference.

The finding is real, but the file on disk has already been changed by the run that reported it. Two things follow, and both surface somewhere else. A gate that should have left the tree alone has dirtied it, so the next target to look at that file sees an edit nobody made. And a person who re-runs the check to confirm gets a pass, because the first run fixed what it was complaining about.

Resolve it

Move the write inside the branch and return from it, so the two runs are visibly different things:

// before: written every time, then judged
fs\writeFile("assets/badge.svg", content: wanted);
if (ctx.hasCharm("rw")) { return; }
if (drift\changedSince(before, ["assets/badge.svg"]).len() > 0) {
    throw "badge is stale";
}

// after: rw writes, everything else only compares
if (ctx.hasCharm("rw")) {
    fs\writeFile("assets/badge.svg", content: wanted);
    return;
}
if (fs\readFile("assets/badge.svg") != wanted) {
    throw "badge is stale; re-run with :rw and commit it";
}

Comparing content directly is usually simpler than hashing before and after, and it cannot be fooled by a write the same run performed.

What this check cannot see

It is deliberately conservative and under-reports:

  • Only fs\writeFile counts. A file written by a subprocess the target forks is invisible here.
  • Only a plain if (ctx.hasCharm("rw")) counts as the branch. A negated or compound condition is not recognized, so a write it guards is reported.
  • A write reached through a helper function is not followed.
  • A target with NO rw branch is never reported. It never claimed to run two ways, and a target that always writes is an ordinary generator.

See also

  • Charms - what rw grants, and why a check runs without it
  • MGS1034 - the sibling mistake: reading a file the target declares as its own output
  • MGS4006 - the drift a gate reports when generated output was not committed
MGS1035magusfilecharmsrwdriftwritesFiles
Last updated (d966bfce)
Glossary

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.

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.

Buzz

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

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.

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.

Conventions

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