---
title: "MGS1019: committed output records its own commit"
description: Fires when a committed generated file contains the repository's own HEAD commit, so regenerating after a commit always reports drift and the build can never be clean.
tags: [MGS1019, magusfile, generated files, drift, doctor, vcs, outputs]
---

# MGS1019: committed output records its own commit

`magus doctor` found a file that is both a **declared output** and **committed**,
and whose bytes contain this repository's **own HEAD commit**.

```text
self-staling outputs
  2 committed output file(s) record the commit that produced them, so regenerating
  after a commit always drifts; untrack them or drop the VCS stamp
    docs: gen/index.html is committed and records this repository's own HEAD commit
    docs: gen/changelog/index.html is committed and records this repository's own HEAD commit
```

Nothing is broken yet. The build simply cannot reach a clean state.

## Why

Committing a source change moves HEAD. HEAD is an input to the generated file. So
the file committed alongside that source is stale the moment it lands, and
regenerating produces a diff. Commit the regenerated output and HEAD moves again.
Amending is not an escape either: a new hash restales the footer that recorded the
previous one.

The only fixed point is a **second commit containing nothing but regenerated
output**, because a commit that does not touch a page's source does not change the
commit that page records. A repository in this state grows a trail of "refresh
generated metadata" commits after every real one. Those commits are the loop's
resting place, not carelessness.

The cost is that every drift gate becomes noise. `magus run generate` fails after a
perfectly ordinary commit, for a diff that contains only a hash, so the signal you
built the gate for is buried under the one it manufactures.

## Fix

**Untrack the output and render where it is published.** The generator keeps its
provenance line, and the publishing job renders from source with the final commit
already known, so there is nothing left to restale. This is what this repository
does with its docs site: `docs/gen/` is generated and gitignored, and
`.github/workflows/cd.yaml` renders it on every push to `main`.

**Or drop the VCS stamp.** If the "Last updated" line is not worth the loop, remove
it and the output becomes a pure function of its sources, which is what a drift gate
wants anyway.

Keeping both and remembering to regenerate is not a third option. The loop is
structural, so the discipline has to hold forever, and when it lapses the committed
file quietly describes a commit it no longer sits in.

## What it checks, and what it does not

The check reads each project's declared outputs
([`AllOutputs`](../../../concepts/cache.md#the-two-roles-of-an-output-maintainer-note),
so per-target `ctx.writesFiles` counts), keeps the ones the VCS **tracks**, and looks
for HEAD's short or full hash in their bytes.

- **Tracked is the whole test.** Untracked generated output records the same hash
  and is fine, because nothing commits it. A check that ignored trackedness would
  fire just as loudly on a repository that had already fixed the problem.
- **Only this repository's HEAD matches.** A lockfile pinning some other project's
  commit, a vendored dependency, a fixture full of digests: none contain your
  current HEAD.
- **An abbreviated hash must stand alone.** A 7-character id found inside a longer
  hex run is not a match, or every file holding a SHA-256 would report.
- **Large and binary files are skipped** (over 1 MiB, or a NUL byte early in the
  file). A provenance stamp lands in rendered text; reading bundles to look for one
  costs time and finds nothing.
- **A backend that cannot report tracked paths skips the check.** It needs
  `types.TrackedFileReporter`, which git implements. Guessing would be worse than
  saying nothing.

## Not a bug when

The file is tracked on purpose and the hash is not this repository's. That is not
this diagnostic; it matches HEAD specifically to avoid exactly that case.

You are mid-release and the stamp is intentional in a file you regenerate once per
tag rather than per commit. The loop is still there, but it turns once a release
instead of once a commit, and you may prefer that to losing the stamp.

## See also

- [Should generated output be committed?](../../../concepts/cache.md#should-generated-output-be-committed)
  - the decision tree this diagnostic is the failure mode of.
- [The self-staling output](../../../concepts/cache.md#the-self-staling-output-generated-files-that-record-vcs-state)
  - the full model, including why amending does not help.
- [CI checkout](../../../guides/integrations/ci.md) - untracking output does not shrink
  what earlier commits already hold; a blobless clone is what stops CI downloading it.
