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

MGS4001: filesystem race condition detected

Two or more concurrently executing projects modified the same git-tracked file while running the same target. The build outcome may depend on which project finished last.

[MGS4001] 2 filesystem race finding(s) (see .../MGS4001.md)
  path=go.work.sum projects=[api,worker] target=build tier=A overlap=34ms
  path=go.sum projects=[api,worker] target=build tier=A overlap=28ms

  Suppress with (.magus/race-allow.yaml):
  - path_pattern: "go.work.sum"
    project_pair: "api,worker"
    reason: ""
  - path_pattern: "go.sum"
    project_pair: "api,worker"
    reason: ""

Why

Magus dispatches projects in parallel whenever the dependency graph allows it. When two projects both run go mod tidy, go work sync, or any other tool that rewrites a shared file (lock files, workspace sums, generated assets), the result depends on which project finishes last. In the best case the file ends up correct but with inconsistent intermediate state; in the worst case the two processes interleave their writes and produce a corrupt file.

This warning fires when the --race observer sees that a git-tracked file was modified during the overlap window of two or more project execution intervals. It is observational: magus does not fail the build, roll back writes, or alter execution order. You decide what to do.

Tier A vs Tier B

  • Tier A fires whenever concurrent writes to the same file are observed. This is the common case and fires on every affected run.
  • Tier B fires when the observed execution ordering of the two projects changed relative to a prior run. A flip means the file's final state differed from last time even though neither project changed, the classic volatility symptom.

Resolution

1. Suppress a known-safe race

If the file is always safe to write concurrently (e.g. an append-only log, or a file that all writers produce identically), add a suppression entry:

# .magus/race-allow.yaml
- path_pattern: "go.work.sum"
  project_pair: "api,worker"
  reason: "go.work.sum is regenerated identically by both projects"

The paste-ready YAML is printed alongside each finding (see example above). path_pattern accepts glob syntax or a bare basename. project_pair is order-independent. Omit project_pair to suppress the pattern for any pair.

2. Serialize the conflicting operations

If the race is real, run the conflicting spell sequentially instead of in parallel. The most common case is a workspace-level sync that must run before individual projects build:

# magusfile.go - run go work sync before any build targets
boosterpack.Bind(pack.Go, spec.Build).After("sync")

Or invoke the sync step explicitly before the parallel build:

magus run sync && magus run build

3. Reduce the shared surface

Restructure the workspace so each project owns its own lock or sum file. For Go modules: ensure each project has its own go.mod with a distinct module path and does not share a go.work.sum with projects that also run go mod tidy.

What this is NOT

  • Not a data-race detector. This warning does not detect Go data races (use go test -race for that). It detects filesystem-level write conflicts between concurrently executing build commands.
  • Not a build failure. Magus does not block or abort the build when this warning fires. It is advisory, like a linter warning.
  • Not always actionable. Some files are safe to write concurrently by design. Use suppressions for those cases.

See also

  • internal/race/: the detector implementation.
  • --race flag on magus run: enables this detector.
  • .magus/race-allow.yaml: suppression list.
  • .magus/cache/race-report.json: full machine-readable findings from the last run (schema 2: includes summary counts and per-finding suppression_snippet).
  • MGS4002.md: eager declared-output overlap check (no --race required).
MGS4001racefilesystemconcurrencyparallelwatchsuppression
Last updated (a170f9b2)
Earlier changes on this page (1)

Full history ↗ · Blame source ↗

Glossary

Workspace

The magus root directory that owns a set of projects and shared config; the unit magus operates over. See workspace.

Project

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

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.

Operation

A single tool-native command a target composes; the middle of the work hierarchy (Spell to Operation to Target). See operations.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. See spells.

Module

A magus stdlib namespace a magusfile imports for host capabilities: filesystem, exec, vcs, and more. See the module reference.

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.

Concurrency

How many targets run at once. It is bounded by the pool's capacity and set with --concurrency, MAGUS_CONCURRENCY, or the concurrency config key. See daemon.

Volatility

A target that fails once and passes on rerun is volatile, as opposed to a regression that started failing and stays failing. magus keeps per-target pass/fail history and a Wilson-score volatility rate to tell them apart and auto-retry the noise. See volatility.

Conventions

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