fix: time out unbounded lane prepare hook - #80
Conversation
Lane prepare used subprocess.run with no timeout. A stuck CLAWBENCH_LANE_PREPARE_CMD blocked lane startup and the EvalWorker. Bound the hook to CLAWBENCH_LANE_PREPARE_TIMEOUT_SECONDS (default 180, matching gateway health) and fail the lane on expiry. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: blocked before merge. Reviewed September 4, 2026, 3:57 AM ET / 07:57 UTC. ClawSweeper reviewWhat this changesAdds a configurable 180-second deadline to evaluation-lane preparation hooks, reports timeout failures, and tests termination of a directly executed hanging hook. Merge readiness⛔ Blocked before merge - 5 items remain The fix remains necessary on current main. The process-group cleanup finding still applies; the earlier container-preflight finding is withdrawn because that unchanged path is not regressed by this PR. The supplied direct-child timeout trace is valid real behavior proof. Priority: P2 Review scores
Verification
How this fits togetherShellBench’s evaluation worker prepares isolated lanes before starting their OpenClaw gateways and running benchmark tasks. Operator-supplied preparation executables receive the lane environment; their completion or failure determines whether startup proceeds. flowchart TD
A[Benchmark job] --> B[Prepare isolated lane]
B --> C[Run configured preparation hook]
C --> D{Completes before deadline?}
D -->|Yes| E[Start gateway and evaluate tasks]
D -->|No| F[Report failure and clean up lane]
Decision needed
Why: Existing hooks are arbitrary operator executables, and the supplied expiry trace does not establish that legitimate installations finish within the new default. Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Own and terminate the hook’s process group, and preserve existing timing behavior through an explicit opt-in deadline unless maintainers approve a default cutoff backed by upgrade evidence. Do we have a high-confidence way to reproduce the issue? Yes, from source: a never-exiting configured hook blocks current main before gateway startup; on the PR, a shell waiting for a child can be killed at timeout while that child survives. No target code or tests were executed during this read-only review. Is this the best way to solve the issue? Partly: placing a deadline at the worker-owned subprocess boundary is appropriate, but direct-PID termination is incomplete and the default cutoff needs an explicit compatibility decision. 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 justifications:
EvidenceWhat 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
HistoryReview history (4 earlier review cycles)
|
What Problem This Solves
When
CLAWBENCH_LANE_PREPARE_CMDis set, each parallel lane runs thatexecutable before the gateway starts.
_run_lane_prepare_hookcalledsubprocess.run([hook], env=hook_env, check=True)with notimeout.If the hook never exits (stuck install, hung network, a sleep that never
returns), lane startup blocks in that child. The EvalWorker cannot start
the gateway, probe
/health, or finish the job.container_lane_eval.shsets this hook for gbrain (
setup_gbrain_runtime.sh). Operators can alsopoint it at any command on a Space or worker.
Sibling timeouts already exist in this repo: gateway health defaults to
180s in the same file, and fleet Crabbox stop uses
run_with_timeout.This hook was the remaining unbounded
subprocess.runon the lanestartup path.
Evidence
Live
python3on this branch pointedCLAWBENCH_LANE_PREPARE_CMDat areal
sleep 1000hook and setCLAWBENCH_LANE_PREPARE_TIMEOUT_SECONDS=1.EvalWorker._run_lane_prepare_hookraised after 1.00s and the hook pidwas gone. The same child under
subprocess.runwithtimeout=1raisedTimeoutExpiredinstead of waiting out the 1000s sleep.Real behavior proof
Behavior or issue addressed: A hung
CLAWBENCH_LANE_PREPARE_CMDcould pin EvalWorker lane startup because_run_lane_prepare_hookused unboundedsubprocess.run. The hook now takestimeout=fromCLAWBENCH_LANE_PREPARE_TIMEOUT_SECONDS(default 180, same as gateway health) and fails the lane withRuntimeErrorafter the child is killed.Real environment tested: macOS 26.6.2 Darwin 25.6.0 arm64, Python 3.14.7, checkout
/tmp/shellbench-F005onfix/prepare-hook-timeout.Exact steps or command run after this patch:
Evidence after fix: terminal output from the live command:
Observed result after fix: The prepare hook returned in 1.00s against a 1000s child. The hook pid was not alive afterward. Production default remains 180s and is overridable through
CLAWBENCH_LANE_PREPARE_TIMEOUT_SECONDS.What was not tested: A live gbrain
setup_gbrain_runtime.shhang inside a container Space.What does this PR do?
Bound
_run_lane_prepare_hookwithsubprocess.run(..., timeout=...)andfail the lane when the hook expires.
Why?
An unbounded prepare hook stalls every later step on that lane. The hang
was introduced in #78
(
c1a79f7,2026-08-31). Related timeout work: #66
(docker cleanup) and #77
(fleet inspect/warmup). CPython documents
subprocess.TimeoutExpiredasthe way to bound
subprocess.run(docs).
Changes
CLAWBENCH_LANE_PREPARE_TIMEOUT_SECONDS(default 180) in_run_lane_prepare_hooktimeout=tosubprocess.runand wrapTimeoutExpiredasRuntimeErrorso the lane fails instead of hangingTests
tests/test_worker.py::test_run_lane_prepare_hook_kills_hung_hookstarts a real
sleep 1000hook, requires the worker to return, andchecks the child pid is gone
python -m pytest -qpasses locallypython -m ruff check clawbench app.py scripts testspasses locally, or the change is docs-only