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

MGS3032: an unordered share of one claimable file

magus job fork, the client tool (magus\job.put) and a magusfile's magus\job.put refused a job because one of its write paths and another live job's write path, held in a different checkout, name the same file, and nothing orders the two jobs against each other:

[MGS3032] job: wave/b declares "run.go", and wave/a (running, updated
  4m3s ago) already holds "run.go". Both are live and neither is the
  other's parent, child, or depends_on partner, so run.go has one
  owner unless the rows say otherwise. wave/a has touched none yet in it so
  far. Claim `run.go#<declaration>` on both rows, add `--depends-on
  wave/a` to wave/b, or fold wave/b into wave/a

Nothing was written to the job store.

Why

A whole-file write path claims the whole file, and two whole-file leases on one file both read "mine wins": neither is ever refused, which is what makes the whole-file spelling look free. It is not free once the file is genuinely shared between two agents working at once, and the plan has three ways to say so: a declaration claim, a depends_on edge, or one job instead of two. This refusal is the fork-time cost that makes naming one of those the cheaper choice. It fires only when magus can prove the fact: the two write paths name one literal file (no glob, no directory, no project root), that file has a diff driver so a declaration claim on it could be graded, and neither row's parent chain nor depends_on already sequences the pair.

What is refused

  • Two whole-file entries on one file, held live in two different checkouts, with neither job the other's parent, child, or depends_on predecessor.
  • A whole-file entry against a claim on that file, as with run.go here and run.go#executeStages there, still collides, since the whole-file entry covers every declaration.

What is not refused

  • A glob, a directory, or a project root. Only a single literal file is judged here; the overlap report and MGS3018 cover the rest.
  • A file with no diff driver (.txt, .json, .toml unless you have registered one). Nothing can grade a declaration of it, so the pair is recorded, not refused; depends_on is the only move.
  • Two claims on different declarations of one file (run.go#A and run.go#B): that is exactly what a claim is for.
  • A pair already ordered, meaning a parent and its child, or two jobs joined by depends_on, directly or through any number of hops, in either direction.
  • Two rows held in the same checkout, or a row not yet taken into one (CheckoutRoot empty). That is what the fork's shared-checkout check and write_proof: overlapping already watch; sharing a file there stays an advisory the orchestrator that declared both rows can act on.
  • A holder blocked on depends_on. A blocked row owns none of its write paths yet, so it cannot collide today; the fork that unblocks it runs this same check again, against whatever holds the file then.
  • A holder whose job has exited. Its holder returned, so its editing is over; the row stays until job wait grades it or its work lands on the base, but it holds no file against a new fork.

The claim pair

When magus can compute one, the refusal prints the claims to use instead of <declaration>. It reads the declarations the holder has touched in the file so far, and the symbols the new job's goals name that the file defines once each, and says whether the two are disjoint:

wave/a has touched func lintRules(ctx int) { in it so far. Claim
`run.go#lintRules` on wave/a and `run.go#lint` on wave/b, which
name different declarations (nothing was written to either row), or add
`--depends-on wave/a` to wave/b, or fold wave/b into wave/a

When both rows reach the same declaration, the refusal says no claim can split them, and the answer is --depends-on or one job. When only one side is known it prints that side's claim and asks for a declaration other than it on the other row; when neither is, it keeps the generic advice above. magus prints the pair and never writes it: the rows are the orchestrator's, so re-fork both with it.

What to do

Claim a declaration on both rows, so the two jobs partition the file instead of racing on it. Claims are the default; --depends-on is for two jobs on the same declaration. Deleting is also a valid route: when removing a code path reaches the goal, the job that owns that declaration deletes it.

magus job fork wave/a --write-paths run.go#lintRules
magus job fork wave/b --write-paths run.go#lint

Or sequence them:

magus job fork wave/b --write-paths run.go --depends-on wave/a

Or fold the work into the job that already holds the file.

In a project's magusfile or magus.yaml the claims are declarations and key paths (magusfile.buzz#project.targets.test, magus.yaml#jobs.stale_after, see MGS3036). A refusal over one of them also opens one attention request asking the holder to release it, with the claims the refused fork wanted; the holder or the root closes it with magus session dispose. A dry run opens none.

See also

  • Leases: write paths, claims, and what the guard enforces.
  • MGS3031: a declaration claim nothing can grade.
  • MGS3018: a directory as a job write path.
MGS3032jobsleaseswrite pathsforkagentsclaimsdepends_on
Last updated (29d8b3de)
Earlier changes on this page (6)

Full history ↗ · Blame source ↗

Glossary

Project

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

Magusfile

The magusfile.buzz that declares a project's targets (as export funs) and binds its spells. Magusfile in full.

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. Target in full.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). Op in full.

Spell

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

Buzz

The language magusfiles are written in (the .buzz engine). Buzz in full.

CI

An ordinary target you compose yourself with ctx.needs; magus does not hardcode its stages. It is the anchor magus affected ci keys off, and ci strips the rw charm. CI in full.

Agent

A model working through an agent host such as Claude Code, Codex, Cursor or OpenCode. magus treats an agent as one more caller of its CLI, MCP tools and console, held to the same checks as a person. Agent in full.

Tool

An action a model can call through its host: a name, a description, a typed input and a result. magus serves its tools over MCP, each a read of the workspace model or a run of the CLI it already has. Tool in full.

Session

An agent host's conversation, by the id the host delivers to its hooks. magus never mints one: a record with no session is unattributed, and the OS user it carries says whose account ran it. Session in full.

Attention request

A durable record that an agent is blocked, opened by a magus session notify event and held until a person disposes it. Nothing closes one on its own. Attention request in full.

Dispose

The human act of closing an attention request: a judgment rendered, recorded with who and why. Distinct from resolving a review thread or a merge conflict: a disposition answers a request; it does not merge anything. Dispose in full.

Job

The unit of delegated work, and one row of the job store: what an orchestrating agent handed out, with its goal, the checkpoint it was cut against, the paths it may write or must not touch, and the one check it runs. A job's holder is either a session, for work an orchestrator handed out, or the daemon, for its own maintenance. The store records; the agent guard is what reads those facts back when grading a write. Job in full.

Run

One target executing under one magus invocation, such as magus run test web or magus affected ci. A run keeps its captured output behind an output reference. Every magus run is a run whether or not any job asked for it; see Job for how the two relate. Run in full.

Lease

The grant a holder takes on a job: the write and read paths that job declared, enforced in the checkout that took it with magus job exec. A job is the piece of work; a lease is permission over it. Lease in full.

Advisor

One read-only check from the advice suite: it reads the changeset through magus and writes one titled section of findings. The same advisors run as a pull request comment in CI and inside magus diff --impact locally. Advisor in full.

Conventions

Placeholders

Angle brackets mark a value you replace with your own; never type the brackets:

magus run <target>
magus completion <shell>    # e.g. bash, zsh, fish

<target>, <path>, <shell>, <name> and the like are stand-ins, not literal text.