20 medium dependency discovery
Part III: Errors & Discoverability | Challenge §20
20. Environment & Dependency Discovery
Severity: Medium | Frequency: Common | Detectability: Easy | Token Spend: Medium | Time: Medium | Context: Low
The Problem
CLI tools often depend on external tools, services, or specific environment configurations. When these are missing, the failure message doesn't tell the agent what's missing or how to get it.
Unhelpful missing dependency errors:
$ tool build
/bin/sh: docker: command not found
exit 127
# Agent knows docker is missing but not: which version, how to install,
# whether there's an alternative
Silent wrong version usage:
$ tool deploy
Deploying...
Error: unsupported field 'replicas' in deployment spec
exit 1
# Actually: kubectl version is too old, but error doesn't say that
Environment check scattered across execution:
$ tool run
Connecting to DB... ok
Loading config... ok
Checking Redis... FAILED: connection refused
# Fails at step 3; agent has to retry to discover more prereqs
Impact
- Agent cannot distinguish a missing dependency from a code bug or network error
- Each unmet prerequisite discovered only at the step that needs it — no early-fail
- No
fixfield means the agent must guess how to resolve the missing dependency - Wrong tool version causes semantic errors (unsupported field, wrong behavior) with no version context
Solutions
Preflight check command:
$ tool doctor --output json
{
"ok": false,
"checks": [
{"name": "docker", "ok": true, "version": "24.0.5", "required": ">=20.0"},
{"name": "kubectl", "ok": false, "found": "1.18.0", "required": ">=1.24",
"fix": "brew upgrade kubectl"},
{"name": "db_conn", "ok": true},
{"name": "redis", "ok": false, "error": "connection refused at localhost:6379",
"fix": "docker run -d redis"}
]
}
Dependency declaration in help:
$ tool build --show-requirements --output json
{
"required": [
{"name": "docker", "version": ">=20.0", "install": "https://docs.docker.com/..."},
{"name": "DOCKER_BUILDX_BUILDER", "type": "env_var", "optional": true}
]
}
For framework design:
- Framework provides a preflight() hook for each command
- tool doctor runs all preflight checks without executing any commands
- Each failed check includes a fix field with the exact command to run
Evaluation
| Score | Condition |
|---|---|
| 0 | No preflight check; missing dependencies discovered mid-execution with unstructured errors; no fix hint |
| 1 | Some dependency errors include the missing tool name; no version info; no structured JSON format |
| 2 | tool doctor --output json runs all checks with ok, version, required, and fix fields |
| 3 | tool <command> --show-requirements --output json lists per-command dependencies; tool doctor is a framework-level command |
Check: Remove or alias a required dependency to a wrong version and run tool doctor --output json — verify it returns a failing check with a fix field containing the exact install command.
Agent Workaround
Run tool doctor --output json before first use; act on fix fields from failing checks:
import subprocess, json, sys
def preflight(tool: str) -> bool:
result = subprocess.run(
[tool, "doctor", "--output", "json"],
capture_output=True, text=True,
)
try:
data = json.loads(result.stdout)
except json.JSONDecodeError:
return True # doctor not supported, assume ok
failing = [c for c in data.get("checks", []) if not c.get("ok")]
for check in failing:
name = check["name"]
fix = check.get("fix", "no fix provided")
found = check.get("found", "not found")
required = check.get("required", "unknown version")
print(f"Prereq failed: {name} (found: {found}, required: {required})")
print(f" Fix: {fix}")
return len(failing) == 0
if not preflight("tool"):
sys.exit(1)
Detect exit 127 (command not found) and map it to a missing dependency:
if result.returncode == 127:
# Shell: command not found — extract missing binary from stderr
missing = result.stderr.strip().split(":")[-1].strip()
raise RuntimeError(f"Missing dependency: {missing} — install it and retry")
Limitation: If the tool has no tool doctor command and exposes dependencies only through runtime failure messages, run a no-op invocation (e.g., tool --version) first and inspect stderr for missing dependency errors before running real commands