Skip to content

fix(local-sandbox): load artifacts into a local agent's container - #60

Merged
earakely-scale merged 6 commits into
mainfrom
edgararakelyan/local-container-exec
Oct 6, 2026
Merged

earakely-scale merged 6 commits into
mainfrom
edgararakelyan/local-container-exec

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

On the local provider, load_artifact into a container left the files on the host or failed. The same was true of the unit-tests verifier:

  • sudo inside scripts. Eight scripts in load_artifact and run_container_unit_tests began with sudo docker …. They all already run under exec_script's sudo bash -c, so the inner sudo adds nothing on providers that keep the outer one (E2B, the Scale sandboxes). On providers that drop it, the inner one breaks the script: locally, the host's sudo asks for a password, and the Modal VM image has no sudo. Every other docker call inside a script, such as run_docker_container's, already ran without it.
  • The container side of docker cp. The local sandbox points /app at its work dir by rewriting script text, and that also changed agent-local-x:/app/x, so the copy targeted a path that doesn't exist in the container.

Changes:

  • The eight scripts no longer write sudo; root comes only from the outer sudo bash -c, which each provider keeps or drops.
  • The five docker cp calls go through a new VmSandbox.docker_cp(source, destination), which hands both paths to a fixed script as arguments. The local sandbox already maps an argument that starts with /app and leaves container:/app/... alone, so it now maps the host side of a copy (the MCP artifact load stages under the host's /app) and never the container's, without reading script text. LocalSandbox itself is unchanged.
  • One container lookup. collect_artifacts, verify_sandbox and the A2A validator each had their own copy; they now share find_agent_container. The validator's copy lacked the check that stops a sandbox that owns its container from taking another a2a-agent-* container on a shared Docker host, so its changelog marker check could read another run's agent.

#37 already kept a reattached local sandbox in VM mode, so collect_artifacts, verify_sandbox and run_code against a local agent already reached its container.

How it was tested

  • make unit-test: 6101 passed. New tests cover docker_cp passing its paths as arguments and raising on failure, the local sandbox mapping only a copy's host side, the shared container lookup, and the validator not reading another run's container.

  • The two slow-tier tests that load an MCP artifact on local compose (test_a_multi_env_deploys_restores_loads_and_tears_down_on_local_compose, test_load_universe_on_single_mcp_server_env[default]): passed.

  • tst/integration/task_step/test_verify_sandbox_local.py, against Docker on macOS: 7 passed. Three new tests: a universe and an EnvironmentArtifact loaded into a local container, and the unit-tests verifier run in one. On main all three fail, two of them with sudo: a terminal is required to read the password.

  • A pipeline run against the real step classes and a remote object and document store: deploy_sandbox → run_docker_container → load_artifact → run_container_unit_tests → collect_artifacts → load_artifact again.

    • load_artifact covers a universe with nested files, a quoted name and a 20 MB blob, plus an EnvironmentArtifact into a path with a space.
    • run_container_unit_tests runs a setup command and checks every file's sha256, its result file is copied out, and a deliberately wrong check must fail.
    • collect_artifacts contents are compared byte for byte.
    Provider main this PR
    local fails: sudo asks for a password passes, also 3 at once on one Docker host (rerun on the final head)
    modal_vm fails: sudo: command not found passes, 5 runs (2 on the final head)
    a Scale-hosted VM provider passes passes, 6 runs (2 on the final head)

    On modal_vm, the EnvironmentArtifact step fails on main and on this PR alike, because the VM image has no python3; it's tracked separately and was left out of those runs. E2B and one other hosted VM provider weren't run. Every sandbox the runs created has exited.

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

The PR should not merge until direct local Docker-copy scripts keep their container paths.

What we checked:

  • Copy paths stay separate: The local and Modal VM providers pass arguments through separately. E2B quotes each argument when it builds its command.

Summary

This PR fixes container file copies and Docker commands for local agent sandboxes, so artifact loads and container checks can reach the agent’s files. It also gives artifact collection, sandbox verification, and A2A changelog checks one shared way to find the agent container.

  • Local sandbox copies now reach the agent container.
  • Agent checks use the sandbox’s own container before any fallback.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Artifact or verifier"] --> B["VmSandbox.docker_cp"]
  B --> C["Host path mapped by local sandbox"]
  B --> D["Container path kept unchanged"]
  E["Direct exec_script docker cp"] --> F["Whole script rewritten"]
Loading

Reviews (6) · Last reviewed commit: "Pass docker cp paths as arguments, not s..."

@earakely-scale
earakely-scale requested a review from a team as a code owner October 6, 2026 05:36
Comment thread src/agent_env/providers/sandbox_providers/local_sandbox.py Outdated
Comment thread src/agent_env/providers/sandbox_providers/local_sandbox.py Outdated
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

Comment thread src/agent_env/providers/sandbox_providers/local_sandbox.py Outdated
Comment thread src/agent_env/providers/sandbox_providers/local_sandbox.py Outdated
earakely-scale and others added 3 commits October 5, 2026 23:30
load_artifact into an agent on the local provider failed: its scripts
start with `sudo docker ...`, which ran this host's sudo, and the /app
rewrite also changed the container side of `docker cp f c:/app/x`, so
the files landed outside the container.

LocalSandbox now drops `sudo` where a bash -c script runs it as a
command, and leaves /app alone after ":" (the container side of a copy
or a -v mount).

collect_artifacts, verify_sandbox and the validator each had their own
agent-container lookup. They now share find_agent_container. The
validator's copy lacked the guard against borrowing another run's
container, so its changelog marker check could read the wrong agent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Dropping sudo from a script also reached a command quoted for a
container (`docker exec c bash -c 'make; sudo make install'`). Only
sudo outside quotes, where this host's shell runs it, is dropped now.

Skipping /app after every ":" also skipped host paths such as
`PATH=$PATH:/app/bin`. Only the container side of a `docker cp` and of
a `-v`/`--volume` mount is skipped now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Eight scripts in load_artifact and the unit-tests verifier began with
`sudo docker`. Each already runs under exec_script's `sudo bash -c`, so
the inner sudo gave no extra privilege where the provider keeps the
outer one (E2B, the Scale sandboxes). Where the provider drops it, the
inner one broke the script: the host's sudo asks for a password
locally, and the Modal VM image has no sudo. The other docker calls in
scripts, such as run_docker_container's, already had none.

With the inner sudo gone, LocalSandbox no longer rewrites sudo in
script text. New local integration tests load an EnvironmentArtifact
into a container and run the unit-tests verifier in one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale force-pushed the edgararakelyan/local-container-exec branch from d4ab287 to 34f702d Compare October 6, 2026 06:34
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

Comment thread src/agent_env/providers/sandbox_providers/local_sandbox.py Outdated
@greptile-apps

This comment has been minimized.

earakely-scale and others added 2 commits October 5, 2026 23:43
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Finding the container side of a copy or mount in script text kept
missing forms. A script that starts with `docker cp` is now treated
like one that starts with `docker exec`: the /app it names is the
container's, and the host side of these copies is a VM temp path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

Comment thread src/agent_env/providers/sandbox_providers/local_sandbox.py Outdated
Leaving a whole `docker cp` script alone broke the MCP artifact load on
the local provider: it stages under the host's /app, which must still
point at the work dir. The five copies now go through
VmSandbox.docker_cp, which hands both paths to a fixed script as
arguments. The local sandbox already maps an argument that starts with
/app and leaves `container:/app/...` alone, so it maps the host side and
never the container's, without reading script text. LocalSandbox is back
to main's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

@earakely-scale

earakely-scale commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator Author

On the summary's remaining point (direct docker cp scripts): every docker cp in core that touches /app now goes through VmSandbox.docker_cp. The one direct script left, snapshot_store.py's docker cp <id>:/var/lib/postgresql/data /tmp/pgdata, has no /app on either side, so the local rewrite leaves it unchanged. A docker cp that a user's own command writes into a script (a bash_cmd, say) is still rewritten as text, exactly as on main. Getting rid of that means no longer rewriting shell text at all, which is a separate change rather than part of this fix.

Verification on ab51673: all CI green, including integration-local-slow.

Step pipeline, dev stage: passes 7 of 7: local ×3 at once, the hosted VM provider ×2, modal_vm ×2.

Full task runs through Task.run, exactly as agent-env task run does it, followed by teardown_run. The task is a 9-server MCP env and its universe load, a claude-code agent, load_artifact into the agent (nested files and a quoted name), two prompts, verify_sandbox with agent_name (a diff -r inside the agent), collect_artifacts with agent_name (hashes compared against the inputs), then a 17-criterion agent judge:

env / agent main this PR
hosted VM / hosted VM agent pass, judge 1.0 pass, judge 1.0
modal_vm / Modal container agent — pass, judge 1.0
local compose / local agent container fails at load_artifact into the agent: Could not find the file …/input in container agent-local-… pass, judge 1.0, all 4 files collected

The local runs used DOCKER_DEFAULT_PLATFORM=linux/amd64, because the agent image is published for amd64 only. Every sandbox exited.

@earakely-scale
earakely-scale merged commit e9a9bf1 into main Oct 6, 2026
13 of 14 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/local-container-exec branch October 6, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant