MGS3011: target exceeded its declared timeout
A target ran longer than the ceiling its magusfile declared. magus cancelled it, killed its process tree, and failed that target. The message carries every input to the decision, so a reader can reconstruct it without opening any code:
[fail] magus security (ran, 15m0s)
cause: [MGS3011] target "security" exceeded its declared timeout of 15m after 15m0s;
its process tree was killed; captured output:
/repo/.magus/logs/9c4f...log
A timeout is a failure. Not a skip, not a warning, and never a pass: a target that was told it may take fifteen minutes and took more has not done its job, and a build tool that reports that as success is worse than one with no ceiling at all.
Why
The incident this exists for: a gate held three project locks for over an hour with zero concurrency slots in use and nothing running. No target failed, so nothing named a culprit, and the invocation had to be killed by hand. The likely cause was a network-bound scanner whose far end stopped answering - a shape with no natural failure mode, because waiting forever is what a socket does.
Nothing else in magus reclaims that. The cache waits on the target, the limiter waits on the cache, and the machine-wide budget (MGS3009) hands out claims that are only released when a run exits. One target that never returns holds all of it.
What was actually killed
The ceiling bounds the whole target, subprocesses included. magus starts every
subprocess in its own process group for exactly this reason, so expiry sends SIGTERM
to the group and then SIGKILL to whatever ignored it - grandchildren the target's
own script forked included. There is no orphaned scanner still holding the network
socket after this fires.
Because the deadline rides the context, a ceiling on the target you SCHEDULED bounds
every target it composes with ctx.needs. A composed target that declares its own,
tighter ceiling is bounded by that one first, and the message then names the composed
target rather than the gate that ran it, which is the difference between "ci hung" and
"security hung".
That tighter ceiling stops where the composed target's own dependencies begin. A
dependency is dispatched once and awaited by everyone that needs it, so a ceiling
declared halfway down the chain would govern work its siblings depend on too: security
declaring fifteen minutes must not decide how long generate may take, because lint,
build and test all wait on the same run of it. A dependency reached this way stays
bounded by the scheduled unit's ceiling, and by the stall watchdog (MGS3012).
Resolution
-
Read the captured log the message names. The ceiling says when magus gave up, not what the target was doing. The log holds the last thing it printed, which is usually the answer.
-
Fix the target, if the log shows it genuinely stopped making progress: an unreachable registry, a scanner database mirror that stalled, a prompt nobody is going to answer.
-
Raise the ceiling, if the target legitimately got slower. Declare a multiple of the worst run on record, never a figure near its typical one:
magus\project({ "targets": { "security": {"timeout": "30m"}, }, });MGS1032 is the check that tells you when a ceiling has drifted into either mistake, measured against the durations magus recorded.
-
Remove the declaration, if the target should not be bounded at all. An undeclared target is unbounded, which is the default and stays the default; nothing infers a ceiling for you.
See also
- MGS1032: a declared ceiling that no longer describes the target.
- MGS3009: the machine-wide budget a hung target holds while it hangs.
- MGS3012: the other half - an invocation making no progress, in work no target declared a ceiling for.
- Spells: what magus bounds, and
target_timeout, the workspace-wide runaway guard this is the per-target form of.