---
title: "MGS3004: tool not ready"
description: A tool is installed but the service it talks to is unreachable, so a spell's readiness probe failed before the op was allowed to run.
tags:
  [MGS3004, readiness, tools, docker, daemon, errors, magusfile]
---

# MGS3004: tool not ready

The binary is present, but the thing it talks to is not. This is
[MGS3003](MGS3003.md) one level deeper: the same category of failure - your
environment rather than your code - caught for a client whose server is down.

```text
[MGS3004] docker is installed but not ready. The op "docker" needs it running, and
`docker info` said:
  Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
  see: https://eli.gladman.cc/magus/reference/codes/sandbox/MGS3004/
```

The probe's own output is carried through rather than paraphrased. `docker info` names
the socket and asks the right question; magus knows less about docker than docker does.
The underlying failure is wrapped, so `errors.Is` still reaches it.

## Why a version probe cannot catch this

A spell's version probe answers "what version is installed", and for a
client/server tool that question is answerable with the server switched off.
`docker --version` is client-only: it prints a version and exits 0 with no
daemon running at all.

So the probe magus already ran was structurally incapable of noticing. The op
forked, docker failed, and the run reported a build failure for a project with
nothing wrong with it - the real explanation sitting in a captured log someone
had to go open.

A **readiness probe** is the third question, distinct from the other two:

| question | declared by | docker |
| --- | --- | --- |
| does it exist? | nothing - PATH lookup ([MGS3003](MGS3003.md)) | `docker` |
| what version? | `mgs_getVersionProbe` | `docker --version` |
| is it usable now? | `mgs_getReadinessProbes` | `docker info` |

## At a terminal, magus waits first

When stdin is a TTY, a failed probe is retried for 30 seconds before the run gives up,
so starting the daemon in another window lets the run continue instead of needing a
re-run.

That grace period is **interactive-only**. Under CI or an agent nobody starts a daemon
mid-run, so waiting there would burn 30 seconds per project to reach the same failure -
and it would make a deterministic failure depend on how fast something else happened to
start. Without a TTY the probe runs once and fails immediately.

Failing fast stays cheap either way: the check runs before the op forks, so nothing was
consumed and a re-run replays from cache.

## Declaring one

Keyed by tool name, the same convention the version probes use, and resolved
through the op's own `Command.bin` - so no op restates which tool it runs.

```buzz
export fun mgs_getReadinessProbes() > {str: Command} {
    return {"docker": Command{bin = "docker", args = ["info"]}};
}
```

Scoping is per **tool**, not per spell, and the docker spell is why. It drives
`docker` and `hadolint`, and linting a Dockerfile talks to no daemon. A
spell-scoped probe would make a lint wait on a service it never uses, which is a
worse version of the bug this exists to fix.

Most spells need none. `go`, `rustc`, and `node` are self-contained; a spell that
declares nothing is never gated.

## Readiness never keys the cache

A readiness result is a **precondition, not an input**. `docker info` reports
running containers and disk usage, so mixing it into a cache key would invalidate
every entry on every run.

This is worth stating because the neighboring mechanism does the opposite: a
[version key](../../../concepts/cache.md) exists precisely to enter the key. The
two probes look alike and mean opposite things.
