Skip to content

fix(verifier): clear result files before running the container verifier - #310

Merged
earakely-scale merged 2 commits into
scaleapi:mainfrom
salma2vec:fix/305-clear-result-files-before-verifier
Oct 23, 2026
Merged

earakely-scale merged 2 commits into
scaleapi:mainfrom
salma2vec:fix/305-clear-result-files-before-verifier

Conversation

@salma2vec

@salma2vec salma2vec commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Closes #305

In reward mode, RunContainerUnitTestsVerifierTaskStep docker cp'd reward_path out of the task container after the verifier command ran, and scored whatever it found. Nothing removed the file first. An agent working in that container could write 1 to the reward path during its turn. If test.sh then exited without writing a reward, the planted file was recorded as score=1.0, result=True for a failed run. That covers a set -e script, or pytest && echo 1 > reward.txt, which relies on "missing file = 0".

This PR removes every entry in result_paths (which includes reward_path) inside the container right before the main command. Anything extracted afterwards was written by this verifier run.

  • The removal runs as docker exec -u root -w / … rm -f -- <paths> via exec_script, so it fails loudly. Root ensures an agent-owned file can be removed. -w / resolves a relative path the same way the later docker cp does.
  • It runs after setup_commands, so a setup step can't leave a stale result behind either. A retried step also no longer reads a previous attempt's file.
  • Exit-code semantics are unchanged. A test.sh that writes a partial reward and exits non-zero is still graded by that reward. I corrected the step-9 comment, which said a non-zero exit is "already recorded as score 0": that only happens when no reward file exists.

How it was tested

  • New test_reward_comes_from_this_verifier_run, parametrized: correct answer, wrong answer, and wrong answer with a planted /logs/verifier/reward.txt. It drives execute() against a fake sandbox that models the container's files: rm -f and docker cp act on them, and the verifier command writes the reward only when the answer is right. On main the planted case scores 1.0 and fails the test. With this change it scores 0.0. The other two cases pass on both.
  • pytest tst/unit packages/agentenv-protocol/tests -n 4: 9455 passed, 49 skipped. 16 failed and 119 errored, all in tst/unit/store/aws/*, store/google_transport_test.py and gcp_secret_manager_secret_store_test.py (SocketBlockedError, no local emulators here). The identical set fails on main.
  • ruff check is clean on both files.

Found and fixed with Claude Code. The runs above were executed locally.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no blocking issues were found.

Summary

Clears reward_path before the verifier command runs, without deleting other requested result files.

  • Reward-mode verifiers start each run with a clear reward file.

Reviews (2) · Last reviewed commit: "fix(verifier): clear only the reward fil..." · Reviewed by Greptile

In reward mode the step read reward_path from the task container after the
verifier ran, without removing it first. A reward file the agent wrote
during its turn was graded as the verifier's when test.sh exited without
writing one (set -e, or 'pytest && echo 1 > reward.txt'): score 1.0 for a
failed run. Remove every result path inside the container before the main
command, and correct the comment that said a non-zero exit is recorded as 0.

Closes scaleapi#305

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@salma2vec
salma2vec requested a review from a team as a code owner October 22, 2026 20:39
Only the reward file is read for the score, so it is the one cleared before the command runs. Other result files stay
as they were: a step can extract what an earlier step's job wrote, as the supervisor steps that adopt a running
engine do, and clearing them made those steps extract nothing. The rm runs as uid 0, which needs no passwd entry,
and the comment no longer claims more than the clearing gives: a process the agent left running can still write the
file.

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

Copy link
Copy Markdown

Thanks for adding the cleanup; the pre-written reward case from #305 now scores 0. Two concerns:

  1. The cleanup removes every result_paths entry, not just reward_path. A task that declares an agent-produced file (e.g. /app/answer.txt) as a result path now loses it before the verifier reads it, so a correct answer scores 0 and the step raises. Limiting the removal to reward_path avoids that.
  2. The removal happens once, before the verifier runs, while the task container is still live, so anything still running there can write the file again. Reading the reward from a location only the verifier can write, or stopping the task's processes before verification, would be more robust.

Happy to share the test cases.

@earakely-scale

Copy link
Copy Markdown
Collaborator

I pushed a3b7365 on top of 58a6c2f with three changes from review:

  • Only reward_path is cleared. Clearing every result_paths entry breaks steps that extract a file an earlier step's job wrote:
    • Prod already relies on this. 33 tasks (about 4,200 verifier steps) have a supervisor step that starts or adopts a long-running engine and hands off every 390 s. The engine, not the step's command, writes /work/events.tail.json, so deleting it before each step made a hand-off step extract nothing.
    • The reward file is enough. The score is read only from reward_path, so clearing that file still closes Reward-mode container verifier scores a reward.txt the agent planted, even when test.sh exits non-zero #305. Every reward-mode step in prod writes its reward from its own command, so clearing it breaks none of them.
  • The comment claims only what the clearing gives: a process the agent left running can still write the reward while the command runs.
  • docker exec -u 0 instead of -u root, which fails with "unable to find user root" in an image with no passwd entry for root.

Verified on an agent-environments-admin VM (m5.2xlarge, linux/amd64), with real Docker, on three trees:

Check main 58a6c2f a3b7365
Real Claude Code (claude-haiku-5-5) in the task container writes 1 to the reward file, then tests that fail without writing one scored 1.0 (#305) the step that reads the planted file has it deleted first, and fails scored 0, local and modal_vm
Engine hand-off: segment 1 starts an engine that writes its tail once, segment 2 adopts it tail extracted tail deleted, so a later step finds none tail extracted
No passwd entry for root, verifier as uid 0, planted reward scored 1.0 step errors (-u root) scored 0
test_verify_sandbox_local.py, 3 new tests (one per row above) 2 fail 2 fail 10/10 pass
Unit tests: tst/unit/task_step/verifiers (405), full unit tier (9,097) pass

@earakely-scale
earakely-scale merged commit ee0abce into scaleapi:main Oct 23, 2026
14 of 15 checks passed
@jimmy-holdout

Copy link
Copy Markdown

Thanks for the quick turnaround. I re-ran the cases from #305 against main after the merge (Ubuntu, network off): a pre-written reward now scores 0 with both the fail-fast and the pass-only verifier scripts, correct answers still score 1, and an answer declared as an extra result path is extracted and scores 1 again. The note about a process still running in the container matches what I see.

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.

Reward-mode container verifier scores a reward.txt the agent planted, even when test.sh exits non-zero

4 participants