---
title: "MGS3005: tool too old"
description: A tool reports a version older than the spell's ops work against, so magus refuses before the op forks rather than letting the tool fail on an unrecognized flag.
tags:
  [MGS3005, version, floor, tools, errors, magusfile]
---

# MGS3005: tool too old

The binary exists, reports a version, and is usable - and that version is older
than the spell's ops support.

```text
[MGS3005] golangci-lint v1.9.0 is older than this spell supports (>= 2.0).
The op "golangci-lint" needs a newer one
```

## The fourth question about a tool

Each is distinct, and only the last two used to have answers:

| question | declared by | code |
| --- | --- | --- |
| does it exist? | nothing - PATH lookup | [MGS3003](MGS3003.md) |
| what version? | `Tool.probe` | - |
| is it usable now? | `Tool.ready` | [MGS3004](MGS3004.md) |
| is it new enough? | `Tool.floor` | MGS3005 |

Without a floor, a too-old binary fails with whatever *it* says about an
unrecognized flag - the same misleading failure [MGS3004](MGS3004.md) exists to
prevent, one question over.

## Declaring one

```buzz
export fun mgs_getTools() > {str: Tool} {
    return {
        "golangci-lint": Tool{
            probe = Command{bin = "golangci-lint", args = ["--version"]},
            floor = ">= 2.0",
        },
    };
}
```

The constraint is the same syntax `magus.yaml`'s `required_version` uses, and a
malformed one is a **load error** rather than a silent pass - a floor nobody can
parse protects nobody.

A floor needs a `probe`: there is nothing to compare without one.

## What it deliberately does not do

**An unprobeable tool is not "too old."** If the probe fails, the floor passes.
Failing there would make a floor a second way for a missing tool to break, with a
worse message than [MGS3003](MGS3003.md)'s.

**Output magus cannot parse is not "too old" either.** A tool whose version line
carries no semver is left alone rather than judged against a constraint it was
never going to satisfy.

The check runs before the op forks and is memoized per tool and directory, so a
declared floor costs one probe per run, not one per target.
