fix: reap timed-out execution-check process groups - #81
Conversation
ExecutionCheck.shell defaults to True. On timeout the runner killed only the shell and then awaited communicate() with no bound, so children holding stdout/stderr wedged scoring. Start the check in its own session, signal the process group, and bound communicate() so verify_completion can return. Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: blocked before merge. Reviewed September 5, 2026, 4:30 AM ET / 08:30 UTC. ClawSweeper reviewWhat this changesThe PR adds shared process-tree cleanup and a bounded output drain to both execution-check runners, with a subprocess regression test. Merge readiness⛔ Blocked before merge - 5 items remain Keep open: current main still contains the timeout hang, and the supplied live evidence demonstrates useful recovery, but three focused correctness repairs remain. Priority: P2 Review scores
Verification
How this fits togetherShellBench runs task-defined commands to verify an agent’s work, then incorporates their results into benchmark scores. Timeout cleanup must stop those commands and return control so scoring can continue. flowchart TD
A[Task verification command] --> B[Execution-check runner]
B --> C[Shell or direct subprocess]
C --> D{Completes before deadline?}
D -->|Yes| E[Evaluate output]
D -->|No| F[Terminate children and drain output]
E --> G[Completion score]
F --> G
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Keep both verifier entrypoints on one bounded cleanup path that terminates owned descendants after their shell exits and remains responsive on Windows. Do we have a high-confidence way to reproduce the issue? Yes, from source: a shell-launched descendant retaining stdout or stderr past the check deadline leaves main’s kill-then-communicate path waiting indefinitely. The supplied Windows comparison supports this mechanism; this review did not execute a current-main reproduction. Is this the best way to solve the issue? Yes in approach: shared process-group termination and bounded draining address the existing failure without adding configuration, but leader-exit handling, Windows cleanup, and the liveness assertion need correction. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: not found in the target repository. Codex review notes: model internal, reasoning high; reviewed against c1a79f731541. LabelsLabel changes:
Label justifications:
EvidenceAcceptance criteria:
What I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
|
What Problem This Solves
ExecutionCheck.shelldefaults to True. Official public tasks such ast1-bugfix-discount(pytest -q) run throughasyncio.create_subprocess_shell. Whentimeout_secondsexpires,run_execution_checkcalledprocess.kill()on the shell only, thenawait process.communicate()with no timeout.A child that still holds stdout or stderr (a hung pytest worker, a
verify script that spawned a helper) keeps those pipes open. Scoring
never returns.
Space
EvalWorkerandclawbench runboth score throughscorer.score_task_run->verify_completion->run_execution_check.One timed-out check wedges the job.
The same timeout handler exists in both
clawbench/environment.pyandclawbench/environment_files.py. Background services already start anew session and
killpgthe group (clawbench/services.py). Executionchecks did not.
Evidence
Live
pythonon this branch compared the old kill-then-unboundedcommunicate()path against the patchedrun_execution_checkon bothpublic runners. The command was a shell-launched Python parent that
spawns a grandchild
sleepinheriting the pipes.The old pattern stayed blocked for the full 5s bound after
process.kill().Both patched runners returned
Timed out after 1sin about 1.1s.Real behavior proof
Behavior or issue addressed: A timed-out execution check could hang scoring because
process.kill()only stopped the shell andcommunicate()waited forever while children held the pipes. The check now starts a process group, signals the group, and boundscommunicate()soverify_completionreturns.Real environment tested: Windows 11, Python 3.13.15, checkout
C:\Users\sebta\.grok\tmp\pr-gate-batch\shellbench-f006onfix/timeout-reap-group.Exact steps or command run after this patch:
Evidence after fix: terminal output from the live command:
Observed result after fix: Scoring returned a timeout result in 1.14s and 1.11s instead of blocking on
communicate(). Exit code is -1 and the reason isTimed out after 1s. The old kill-only path was still blocked after 5.00s.What was not tested: A live Hugging Face Space EvalWorker scoring
t1-bugfix-discountagainst a hung pytest worker.What does this PR do?
Reap timed-out execution-check process groups and bound
process.communicate()so scoring can return.Why?
This hang was introduced in
2e39d5cc(2026-04-09, 149 days ago) andcopied into
environment_files.pyby #17(
56531fbf, 2026-04-29). Related timeout work:#66 (docker cleanup),
#77 (fleet inspect/warmup),
#80 (lane prepare hook).
None of those cover execution-check timeout reap.
Python documents that
Popen.kill()signals only that process(docs).
A shell-spawned child that inherits the pipes keeps
communicate()blocked until those writers exit.
Changes
start_new_session=Trueon POSIXkillpgon POSIX,taskkill /Ton Windows)communicate()withasyncio.wait_forTests
tests/test_execution_shell_rendering.py::test_execution_check_timeout_reaps_shell_child_process_groupstarts a real shell child that holds the pipes, requires scoring to
return a timeout result, and on POSIX checks the grandchild is gone
python -m ruff check clawbench app.py scripts testspasses locally