MGS3004: tool not ready
The binary is present, but the thing it talks to is not. This is MGS3003 one level deeper: the same category of failure - your environment rather than your code - caught for a client whose server is down.
[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) | 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.
Resolution
The probe's own output is the instruction: start the service it names - the
socket in docker info's complaint above - and run again. Nothing was consumed,
because the check runs before the op forks, so the re-run replays from cache. At
a terminal you do not need a re-run at all: start the service in another window
inside the 30-second grace period and the run continues.
If the failure instead reached you as a build error out of the tool itself, no readiness probe is declared for it. Declare one, as below.
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.
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 exists precisely to enter the key. The two probes look alike and mean opposite things.