Symptom
Engine bin resolution depends on whatever PATH launched ringer. A binary that works interactively (e.g. opencode in ~/.local/bin) can be invisible to ringer launched from a different context, and the same manifest then works or 127s based on invisible state. Hit twice in two days in the field (2026-07-13/14) — the second time because the fix lived in the operator's shell, not anywhere durable.
preflight_engine_bins catches the direct case at run start (and #36 adds a circuit breaker for the wrapper-script case it can't see), but the earliest, cheapest signal is config load itself.
Proposed fix
- When the config loader resolves an
[engines.<name>] block whose bin is a bare name, warn immediately if shutil.which() can't resolve it — in lint, doctor-style output, and at run startup — printing the PATH searched and the config key to fix.
- Optional: a per-engine
path_prepend key so an engine's PATH requirement lives in config (durable, versioned) instead of the launching shell.
Size: S.
🤖 Generated with Claude Code
Symptom
Engine
binresolution depends on whatever PATH launched ringer. A binary that works interactively (e.g.opencodein~/.local/bin) can be invisible to ringer launched from a different context, and the same manifest then works or 127s based on invisible state. Hit twice in two days in the field (2026-07-13/14) — the second time because the fix lived in the operator's shell, not anywhere durable.preflight_engine_binscatches the direct case at run start (and #36 adds a circuit breaker for the wrapper-script case it can't see), but the earliest, cheapest signal is config load itself.Proposed fix
[engines.<name>]block whosebinis a bare name, warn immediately ifshutil.which()can't resolve it — inlint,doctor-style output, and atrunstartup — printing the PATH searched and the config key to fix.path_prependkey so an engine's PATH requirement lives in config (durable, versioned) instead of the launching shell.Size: S.
🤖 Generated with Claude Code