fix(runner): resolve the container engine on use, not on import - #339
Open
vaibhavdabas16 wants to merge 1 commit into
Open
fix(runner): resolve the container engine on use, not on import#339vaibhavdabas16 wants to merge 1 commit into
vaibhavdabas16 wants to merge 1 commit into
Conversation
run_support/config.py ran engine detection at module scope:
ENGINE = _detect_engine()
and _detect_engine ends in a bare sys.exit(1) when neither docker nor
podman is on PATH. Importing config -- or anything that transitively
imports it, which is docker.py, metadata.py and run.py -- therefore
terminated the interpreter on a host with no container runtime. Not an
exception a caller could catch and degrade on.
Replace the constant with a cached engine() function and call it at the
16 sites that actually shell out to a container. The probe now happens on
first use, where the failure belongs to the operation that needs an
engine, and importing the module is free of side effects.
Three follow-on cleanups fall out of that:
- batch.py's detect_engine() was a third copy of the same PATH probe,
kept only because importing config was expensive. It is now an alias
for config.engine().
- _detect_engine sniffed sys.argv for -h/--help and returned a guessed
engine so help output would work without Docker. With lazy resolution
nothing asks for an engine before argparse exits, so the special case
is gone.
- Three tests reloaded modules and monkeypatched shutil.which purely to
survive the import probe. They now pin module.engine directly.
config.ENGINE is kept for one release as a module __getattr__ shim that
warns and returns engine(); nothing in this repo reads it any more.
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #315.
run_support/config.pyran engine detection as a side effect of import:and
_detect_engineends in a baresys.exit(1)when neither docker nor podman is on PATH. So importingconfig— or anything that transitively imports it — killed the interpreter on a host with no container runtime. Not an exception a caller could catch and degrade on.This makes resolution lazy and cached (
config.engine()), and calls it at the 16 sites that actually shell out to a container.Answering @Perry2004's question on the issue
That is exactly right, and it is the reason to make the change rather than an argument against it — the entrypoints that need an engine still fail fast, and this is what the current design costs the ones that don't. Measured on
main, not argued from principle:batchandrescoreimport fine only because they were kept away fromconfig. That avoidance has a price already paid in the tree:eval/rescore.pygrew its ownyaml.safe_loadofmodels.yamlrather than reuseload_model_config— the divergence reported in rescore: public reproducibility CLI defaults to a maintainer's home paths and silently no-ops elsewhere #296.runner/batch.pycarried a third copy of the PATH probe (config._detect_engine,batch.detect_engine, andtui._engine_from_env_or_pathare the same logic three times, and batch's even had a different error string). This PR deletes batch's copy:detect_engine = engine.shutil.whichpurely to survive the import. They now pinmodule.enginedirectly.So the change does not weaken fail-fast for
clawbench-run/clawbench-batch; it stops the probe from being a tax on every module that merely sits in the same import graph.What changed
run_support/config.pyENGINE = _detect_engine()at module scope@lru_cache engine()run_support/docker.pyENGINEengine()runner/run.pyengine()run_support/metadata.pyengine()runner/batch.pydetect_engine = engineTwo things fall out that the issue did not ask for but are direct consequences:
_detect_enginesniffedsys.argvfor-h/--helpand returned a guessed engine so help output would work without Docker. With lazy resolution nothing asks for an engine before argparse exits, so the special case is gone.test_module_help_does_not_require_container_runtimestill passes — now for the real reason instead of the workaround.shutil.whichwalks PATH each time.engine.cache_clear()is documented for the one process that changesCONTAINER_ENGINEmid-run (batch, when it pins the engine for child processes).config.ENGINEis kept for one release as a module__getattr__shim that warns and returnsengine(), as the issue suggested. Nothing in this repo reads it any more. Because it only fires on attribute access, importing the module stays free of the probe.Verification
213 passed, 4 skipped— unchanged frommainon this machine. (test_host_tasks.py::…[v1-lite]fails onmaintoo: thev1-litesymlinks check out as text files on Windows. Unrelated, not touched.)13 new tests in
tests/test_container_engine_resolution.py. The core one imports each of the six affected modules in a fresh interpreter withshutil.whichstubbed toNone. Against the pre-change source,config,docker,metadataandrunall die at import;batchandrescorepass only by avoidance, and now pass while importingconfigdirectly — which is the property worth guarding.The rest cover:
engine()still exits 1 when no engine exists at call time,CONTAINER_ENGINEstill wins and is still validated, PATH is probed once, the deprecatedENGINEwarns, and an unknown attribute still raisesAttributeErrorrather than being swallowed by the shim.Merge notes
Touches
runner/batch.py, as does #340 (different functions — imports anddetect_enginehere,print_run_stats/write_summary_jsonthere).CHANGELOG.mdwill conflict textually with any other open PR of mine; trivial to resolve either way.