---
title: "MGS1035: a target writes the tree when it was given no rw charm"
description: Fires when a target branches on the rw charm but calls fs\writeFile outside that branch. The run that was meant only to judge the tree edits it first, reports the edit as a finding, and leaves it behind.
tags: [MGS1035, magusfile, charms, rw, drift, writesFiles]
---

# 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:

```text
[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:

```buzz
// 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](../../../concepts/charms.md) - what `rw` grants, and why a check runs without it
- [MGS1034](MGS1034.md) - the sibling mistake: reading a file the target declares as its own output
- [MGS4006](../race/MGS4006.md) - the drift a gate reports when generated output was not committed
