---
title: "MGS3011: target exceeded its declared timeout"
description: Fires when a target runs longer than the timeout its magusfile declared, so magus kills its process tree and fails that target rather than letting the invocation hang.
tags: [MGS3011, timeout, target_timeout, runaway, hang, locks, process group, declarations]
---

# 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:

```text
[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](MGS3009.md)) 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 a target also 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".

## Resolution

1. **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.

2. **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.

3. **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:

   ```buzz
   magus\project({
       "targets": {
           "security": {"timeout": "30m"},
       },
   });
   ```

   [MGS1032](../magusfile/MGS1032.md) is the check that tells you when a ceiling has
   drifted into either mistake, measured against the durations magus recorded.

4. **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](../magusfile/MGS1032.md): a declared ceiling that no longer describes the
  target.
- [MGS3009](MGS3009.md): the machine-wide budget a hung target holds while it hangs.
- [MGS3012](MGS3012.md): the other half - an invocation making no progress, in work no
  target declared a ceiling for.
- [Spells](../../../concepts/spells.md): what magus bounds, and `target_timeout`, the
  workspace-wide runaway guard this is the per-target form of.
