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

MGS4003: non-deterministic output

A project's declared outputs produced different content across two consecutive runs of the same dispatch. The same inputs should yield the same outputs; magus treats determinism as a build-system invariant.

[MGS4003] non-deterministic output (see .../MGS4003.md)
  project=api target=build differing_paths=[api/dist/bundle.js api/dist/manifest.json]

Why

This warning is only emitted by --race=replay, which:

  1. Runs the affected set normally (concurrent, cache-aware).
  2. Content-hashes every declared output of every project.
  3. Re-executes the same set sequentially, bypassing the cache.
  4. Content-hashes the outputs again.
  5. Compares the two hash sets per project.

Any project whose outputs differ between the two runs is non-deterministic. Common causes:

  • Embedded timestamps. A build that injects time.Now() or __DATE__ into the artifact (linker build IDs, pkg/build.go ldflags, source maps with generation timestamps).
  • Map iteration order. Go and Python iterate maps in random order; a generator that emits code from a map without sorting keys will produce different bytes each run.
  • Parallel work-stealing leaking into output. Worker IDs, goroutine IDs, or the order of fan-in results landing in a manifest.
  • PID / hostname / cwd in the output. Some toolchains embed these by default (e.g. pip embeds the build tempdir path into compiled .pyc).
  • Random salt / nonce. Crypto helpers used at build time without a fixed seed.

This check is expensive: it doubles the wall-clock time of the affected build, and the second pass cannot benefit from the cache. Run it for CI nightly or a manual audit rather than every push.

Resolution

1. Identify the source

The differing_paths list narrows the search. Open one of the differing files in both runs and diff them. The divergence is often visible at first glance (a timestamp, a permuted list, a tempdir path).

For binary outputs, use diffoscope (the reproducible-builds.org tool) which unpacks archives, decompresses sections, and diffs the contents recursively:

magus run build
cp -r api/dist /tmp/run1
magus run build --race=replay   # second run rebuilds
diffoscope /tmp/run1/bundle.js api/dist/bundle.js

2. Remove the source of non-determinism

Common fixes:

  • Embedded timestamps: ldflag the build time to a fixed value (-X main.BuildTime=$(git log -1 --format=%cI) ties it to the commit timestamp, which is deterministic).
  • Map iteration: sort keys before emitting (slices.Sort(maps.Keys(m))).
  • Tempdir paths: use a fixed relative path or strip the absolute prefix.
  • Build IDs / PIDs: pass -buildid= to go build and -Wl,--build-id=none to the linker.

3. If non-determinism is unavoidable

Some artifacts (e.g. compiler outputs with debug info on Windows) are non-deterministic by design. For these, exclude the path from the project's declared outputs so the cache doesn't track them.

What this is NOT

  • Not a runtime crash detector. It only checks that outputs are bit-for-bit identical, not that they behave the same.
  • Not run by default. You must pass --race=replay explicitly.
  • Not a substitute for reproducible-builds.org tooling. That community has stronger guarantees (cross-machine, cross-time, cross-locale). MGS4003 is a same-machine same-session check.

See also

MGS4003racedeterminismreplayreproducible buildsoutputshashing
Last updated (b022d75e)
Glossary

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. See workspace.

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.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

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.

Conventions

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