Resolve Harbor ${VAR} env templates from the runtime environment - #581
Open
ayushnangia wants to merge 2 commits into
Open
Resolve Harbor ${VAR} env templates from the runtime environment#581ayushnangia wants to merge 2 commits into
ayushnangia wants to merge 2 commits into
Conversation
Compose environments stage the values into the launch-time override; image environments pass them as --env arguments. Without this there is no way to hand a local Compose run a host value at all (run_args is rejected for Compose).
Harbor task env values that are exactly ${VAR} or ${VAR:-default}
resolve from the host environment when a trial starts. The adapter
passed them through verbatim, so agents and verifiers saw the literal
template string and LLM-judge verifiers silently failed auth.
Resolution happens in env.py at startup with Harbor's fullmatch
semantics, sourced from the container process env the runtime's
env_vars populate. Templates stay verbatim in the content-hashed
manifest and persisted task rows, so host secrets never enter an
image or a task file; a missing required variable aborts startup
naming the variable.
jdchawla29
added a commit
that referenced
this pull request
Aug 15, 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.
Harbor interop: resolve
${VAR}/${VAR:-default}env templatesProblem
Harbor's task schema supports environment variable templates in every
envmap —
env = { OPENAI_API_KEY = "${OPENAI_API_KEY}" }— resolved from thehost environment when a trial starts (
harbor/utils/env.py:resolve_env_vars,applied at sandbox creation and again just before the verifier runs). This is
how real Harbor datasets inject LLM-judge credentials: among the adapters
shipped in harbor 0.20.0,
hle,clbench,tau3-bench,theagentcompany,strongreject,seal0,ineqmath,crmarena,scienceagentbenchandothers all template their verifier or environment env this way.
The HUD adapter passes these values through verbatim:
adapt.pycopies theraw dicts into the baked manifest, and
env.pyapplies them withos.environ.update(...). An agent or verifier then sees the literal string${OPENAI_API_KEY}, and an LLM-judge verifier fails auth (typically scoring0.0) instead of grading — silently, per task, with no signal that the task
config asked for a host credential.
What resolution has to respect
Two HUD invariants rule out the obvious fixes:
content-hashed image payload, and task rows persist to
tasks.json.Harbor has
templatize_sensitive_envfor exactly this concern — templatesare the safe form to persist. (A regression test now pins this: host
values must never appear in the adapt output tree.)
compose.jsonfiles must stay hermetic (ComposeConfig.from_filerejects$interpolation for remote adaptation), so the templates can't be pushedinto the Compose document for
docker composeto resolve.The channel that already exists for launch-time host values is the runtime
provider:
ModalRuntime(env_vars=...)stages values into the Composeoverride, which lives only in a temp directory for the duration of
up.Change
DockerRuntimegainsenv_varswith the same meaning asModalRuntime's existing parameter: applied to the Compose override forCompose environments (
ComposeProject.stagealready supported this) andas
--envarguments for image environments. Without this there is no wayto hand a local Compose run a host value at all (
run_argsis rejectedfor Compose).
env.pyports Harbor's resolution semantics and applies them to theenvironment,agent, andverifierenv maps at startup, beforeanything consumes them. Matching
resolve_env_vars: only values that areexactly
${VAR}or${VAR:-default}resolve (embedded templates stayliteral);
${VAR:-}yields an empty string; a required variable with nodefault and no value fails loudly with the variable's name. The source is
the control process's environment — populated by the runtime's
env_vars— so resolution is equivalent to Harbor's host-side passwithout any secret entering a persisted artifact.
One deliberate divergence: Harbor resolves the verifier env just before the
test runs, so a missing judge credential surfaces only after the agent has
done all its work. Here all three maps resolve at environment startup, so a
missing variable aborts before a rollout spends anything.
Usage matches Harbor's
harbor runon the same dataset:Tests
test_docker_runtime_passes_env_vars_to_docker_run/test_docker_runtime_stages_env_vars_into_the_compose_override: the newparameter reaches both Docker paths (scripted docker CLI, no daemon).
test_env_templates_are_persisted_verbatim_not_resolved: adapt outputnever contains a host value that a template references, only the template.
test_env_templates_resolve_from_runtime_env_vars(integration): a taskwhose
task.tomltemplates[environment.env]and[verifier.env]runsend-to-end; the verifier asserts the resolved value, the default, the
empty default, and that an embedded
Bearer ${...}stays literal; theagent-side workspace sees the resolved values too. Fails on current main
(verifier sees literal templates, reward 0.0).
test_missing_env_template_aborts_startup(integration): a requiredtemplate with no value aborts the adapted artifact's main service with an
error naming the variable. The test runs the generated Compose service in
the foreground because providers yield as soon as the published port
exists — readiness is client-owned — so an early abort is only observable
from the artifact itself.
uv run pytest -q,-m integration(Docker), ruff format/check, andty checkall pass.Note
Medium Risk
Changes how credentials reach Harbor verifiers at runtime; behavior is well-tested but misconfigured env_vars could still break grading or leak expectations about which host vars are required.
Overview
Harbor tasks often declare env values like
${OPENAI_API_KEY}that must be filled from the host at trial start. Before this change, those strings stayed literal in the adapted image, so LLM-judge verifiers could fail auth with no clear signal.DockerRuntimenow acceptsenv_vars, aligned withModalRuntime: values go into the Compose override for Compose runs and asdocker run --envfor image-only runs, so host secrets never need to be baked into content-hashed manifests.In
harbor/env.py, startup applies Harbor-style resolution forenvironment,agent, andverifierenv maps: only values that are exactly${VAR}or${VAR:-default}resolve from the control process env (populated by the provider); embedded templates stay literal; missing required variables raise an error naming the variable. Adapt output still stores templates verbatim—host values are not persisted in the image tree.Tests cover Docker wiring, adapt-time non-leakage of secrets, end-to-end template resolution via
DockerRuntime(env_vars=...), and startup failure when a required template has no value.Reviewed by Cursor Bugbot for commit 285aa24. Bugbot is set up for automated code reviews on this repo. Configure here.