---
title: "MGS1036: a narrowed footprint dropped the files its own ops read"
description: A target declares ctx.readsFiles, which replaces the project baseline, and then runs a spell op over file kinds that footprint never names. The op replays instead of running, and the gate stays green.
tags: [MGS1036, magusfile, sources, cache, readsFiles, footprint, doctor]
---

# MGS1036: a narrowed footprint dropped the files its own ops read

A target declares its own footprint with `ctx.readsFiles(...)` and then calls a spell op
that reads a kind of file the footprint never names. Editing those files does not move the
key, so the op replays rather than runs.

```text
[fail] footprint-drops-op-globs: 1 target(s) replace their cache footprint and then run an
       op over files that footprint never names, so an edit to those files replays the op
       instead of running it
  libs/testlayout: format declares its own footprint and calls go[go-fmt], whose spell
  reads **/*.go **/*.txtar **/*.s **/*.S **/*.c **/*.h go.mod go.sum go.work go.work.sum;
  no declared glob names any of them
```

## Why this matters

`ctx.readsFiles` is an **ownership boundary, not an extra margin**. The moment a target
declares one, `buildStep` resets that target's sources to the magusfiles and folds back
only the refs the body declared plus the spell sources specific to **that target**. The
project-wide baseline is gone, and so is every glob a spell contributes project-wide.

Most spells contribute project-wide. The go spell's `mgs_listRequiredGlobs` takes no
target argument, so every glob it has is project-wide, and a target that narrows its
footprint keeps none of them unless it says so.

What makes this worth a code rather than a comment is that **the failure is green**. The
op is skipped, not failed. A sibling target that kept the baseline still re-runs, so a
`ci` composing both passes while the formatter or the suite never saw the change:

- A `format` narrowed to `**/*.md` still calls `go-fmt`. A pure-Go edit replays it, so
  nothing is reformatted, and `lint`, `build` and `test` all re-run and pass.
- A `test` narrowed to four console files drops `**/*.go`. Measured in this repository:
  about 8,400 new lines of `*_test.go` landed and `magus run test .` replayed the cached
  verdict, coverage profile untouched, until `--no-cache`.

## What it does not report

**Narrowing on purpose.** A footprint naming one `*.go` path has said what it reads; the
check cannot know whether that path is the right one, and does not guess. It fires only
when the footprint names **no** file of any kind the spell reads, which is the difference
between a claim an author made and one they forgot.

**`ctx.modifiesExistingFiles`.** Those refs fold into the target's sources exactly as
inputs do, so a target that declares what it _edits_ has keyed those files. That is the
correct declaration for `gofmt` and `dprint`, which amend authored files rather than
producing them, and reporting it would be reporting the fix.

**A target with no footprint of its own.** Without `ctx.readsFiles` the project baseline
keys the target and there is nothing to drop.

**A spell that declares globs per target.** Those survive the reset (`WithTargetSources`),
so such a spell loses nothing.

## Resolve it

Name what the ops read, in the declaration that matches the relationship:

```buzz
export fun format(ctx: magus\Context, args: [str]) > void {
    ctx.readsFiles("dprint.json");
    // gofmt and dprint AMEND authored files rather than producing them, so these are
    // modifiesExistingFiles: the bytes key the target without joining the snapshot.
    ctx.modifiesExistingFiles("**/*.md", "**/*.go", "go.mod", "go.sum");
    go["go-fmt"](ctx);
    markdown["dprint"](ctx);
}
```

Declaring the files project-wide does **not** fix it. A project-wide `sources` entry
reaches only the targets that kept the baseline, which are the ones that do not read the
file; the target that does read it replaced its footprint and never sees the entry. That
is also why [MGS1028](MGS1028.md) can go quiet on a declaration that keys nothing: its
check unions project sources with per-target refs, and a file declared in the wrong half
still counts as declared.

If the target genuinely does not read those files, the op does not belong in it.

## See also

- [Cache model](../../../concepts/cache.md#per-target-inputs-and-outputs) - what a
  per-target footprint replaces, and the rule for where a glob belongs
- [MGS1004](MGS1004.md) - a footprint declaration the loader never reaches
- [MGS1028](MGS1028.md) - a changed file that seeds a project it does not key
- [MGS1029](MGS1029.md) - a source glob rooted inside a pruned directory
- [MGS1034](MGS1034.md) - a target reading a file it declares as its own output
