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