---
title: "MGS3003: tool not on PATH"
description: os.which could not resolve a command against PATH, so the tool a target depends on is not installed or not visible to this run.
tags:
  [MGS3003, path, tools, host-modules, errors, magusfile]
---

# MGS3003: tool not on PATH

`os\which` could not resolve a command. The tool is not installed, or not
visible to this run - a sandboxed target sees a curated PATH, so "installed on
your machine" and "reachable from here" are different questions.

```text
[MGS3003] "vhs" is not on PATH: exec: "vhs": executable file not found in $PATH
```

## Why this raises instead of returning ""

`os\which` used to answer `""` for a missing command so a magusfile could
branch on `os\which(cmd) == ""`. That put the check on every call site and made
it optional - skip it and the empty string flows straight into an `os\exec` or
a path join, and the failure surfaces as something unrelated.

## Resolution

Wrap it where a missing tool is a case you want to explain. The point of
checking at all is to replace a cryptic exec failure with a sentence that says
how to fix it:

```buzz
try {
    os\which("vhs");
} catch (e) {
    throw "tapes: vhs not found on PATH; install it with `brew install vhs`";
}
```

If the tool should always be present, do not catch. The raised error already
names the command and the reason, which is a better failure than a downstream
exec error nobody can trace back.

## Pinning the tool instead

A tool a target needs should usually be pinned in `mise.toml` rather than
assumed. A pinned tool is installed by `mise install` in CI and resolves the
same way on every machine, which turns this diagnostic from a routine
occurrence into a genuine misconfiguration.

## Catching it by code

```buzz
try {
    final p = os\which("docker");
} catch (e) {
    if (e["code"] == "MGS3003") {
        magus\info("docker is not available; skipping the container checks");
    }
}
```
