Finding 1 · H-2026-004
scicode: raise SystemExit(0) passes every sub-step
What is rewarded
A sub-step passes when its program exits with code 0, and nothing confirms the asserts ran. One line, raise SystemExit(0), passes the sub-step before its tests run, and because each step’s code carries into later steps, every later sub-step passes too.
Reproduction
# run from the repo root, with resources_servers/scicode on PYTHONPATH
import sys
from resources_servers.scicode.scicode_integration.runner import run_substep
# A sub-step program whose only test is `assert False`,
# preceded by the "solution" `raise SystemExit(0)`.
program = "raise SystemExit(0)\n\nassert False, 'tests must not pass'\n"
print(run_substep(program, timeout_secs=30, python_executable=sys.executable))Evidence
Output
{'passed': True, 'error': '', 'infrastructure_error': False, 'returncode': 0, 'timed_out': False, 'duration_s': 0.0629999999946449, 'exception_class': None, 'stdout_tail': '', 'stderr_tail': ''}The test is assert False, and the sub-step still passes. Across the 5 problems in resources_servers/scicode/data/example.jsonl, a solution that starts with raise SystemExit(0) passed 13 of 13 scored sub-steps (reward 1.0 on 5 of 5), the same as correct code. A wrong-answer control passed 0 of 13.
Root cause
build_test_program (runner.py:310) assembles one program: the agent’s code, then the target loader, then the sub-step’s asserts. run_substep (runner.py:320) runs it and sets passed = proc.returncode == 0 (runner.py:390). Nothing confirms the asserts ran, so code that exits with 0 before reaching them passes.
The agent’s extract_python_script strips only import lines, so the exit survives, and because each step’s code is carried into later steps, one such line passes every later sub-step too. The default scicode config is affected; scicodepile uses the same server.
Proposed fix
Append a per-run random value after the last assert and require it in stdout along with exit code 0. This also covers sys.exit(), exit(), os._exit(0) and atexit tricks. The alternative: run the tests from a harness that executes the solution inside try/except BaseException (which catches SystemExit) and reports results through a channel the solution can’t write to.
@@ run_substep(), line 338 @@ source_path = None started = time.monotonic()Added: sentinel = secrets.token_hex(16) # new every runAdded: program += f"\nprint({sentinel!r})\n" # after the last assert try:@@ line 389 @@ duration_s = time.monotonic() - startedRemoved: passed = proc.returncode == 0Added: passed = proc.returncode == 0 and sentinel in _decode(proc.stdout) error = "" if passed else _bounded_stderr(proc.stderr)
Proposed, not merged. The sentinel sits in the same program file as the agent’s code, so code that reads its own source file could still find it; the harness option closes that too. The same exit-code rule exists in the upstream SciCode / nemo-skills harness.
Regression test
Written to fail on 92407083 and pass once a fix is in; the last test checks that correct work still passes. Added for this report, not part of the issue.
import sys
import pytest
from resources_servers.scicode.scicode_integration.runner import run_substep
EXITS = ["raise SystemExit(0)", "import sys; sys.exit()", "import os; os._exit(0)"]
@pytest.mark.parametrize("exit_line", EXITS)
def test_early_exit_does_not_pass(exit_line):
program = f"{exit_line}\n\nassert False, 'tests must not pass'\n"
result = run_substep(program, timeout_secs=30, python_executable=sys.executable)
assert result["passed"] is False
def test_asserts_that_run_still_pass():
result = run_substep("assert 1 + 1 == 2\n", timeout_secs=30, python_executable=sys.executable)
assert result["passed"] is True