fix(supervisor): bound run_once child wait - #137
Conversation
ocm service --once blocked forever on child.wait() if an env child never exited. Wait with a deadline and kill the child on timeout, matching the handoff wait helper. Signed-off-by: Sebastien Tardif <[email protected]>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs real behavior proof before merge. Reviewed September 2, 2026, 6:53 PM ET / 22:53 UTC. ClawSweeper reviewWhat this changesThe PR replaces the once-only supervisor’s unbounded child wait with a 15-second timed wait that terminates a stuck child and adds Unix tests. Merge readiness⛔ Blocked before merge - 4 items remain This PR fixes a real unbounded wait, but its new timeout cleanup duplicates and weakens the supervisor’s established process-group shutdown invariant. It also still lacks real proof through the actual once-only OCM command path. Priority: P2 Review scores
Verification
How this fits togetherOCM’s supervisor reads persisted environment-child specifications and starts each configured OpenClaw process. In once-only mode it waits for those children, then returns a run summary or an error to the service command. flowchart LR
A[Persisted supervisor state] --> B[Once-only service run]
B --> C[Start environment child]
C --> D[Timed child wait]
D -->|child exits| E[Run summary]
D -->|deadline expires| F[Terminate child group]
F --> G[Service error]
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Reuse or extract the established process-group shutdown behavior, then add a production-spawn regression that proves a TERM-ignoring descendant is gone before the timeout error is returned. Do we have a high-confidence way to reproduce the issue? Yes, at source level: the PR replaces the previous unconditional child wait in the once-only run path with a deadline. The supplied terminal trace exercises a standalone waiter and helper test, not the full persisted-state OCM command path. Is this the best way to solve the issue? Mostly yes: bounding the once-only wait is the narrow repair, but it should preserve the repository’s existing whole-process-group cleanup invariant rather than introduce a weaker parallel implementation. 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 4d5defce1237. LabelsLabel 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
HistoryReview history (1 earlier review cycle)
|
The 200ms deadline plus SIGTERM grace can exceed 1s on macos-latest. Keep proving we do not wait the full 30s sleep. Signed-off-by: Sebastien Tardif <[email protected]>
What Problem This Solves
Fixes an issue where
ocm service --once(the__daemon run --oncepath) would hang forever when a planned env child never exited. After spawn,run_oncecalledchild.wait()with no deadline.On origin/main the hang is here:
ocm/src/supervisor/mod.rs
Lines 809 to 811 in e04c101
e04c101
This is separate from PR #136, which bounds git worktree helpers.
Why This Change Was Made
run_oncenow waits throughwait_child_with_timeout. The child is polled, then terminated (process group TERM, then KILL) whenSERVICE_ONCE_CHILD_TIMEOUT_MS(15s) expires, matching the restart-handoff wait helper. On timeout the supervisor returns an error instead of blocking. Long-runningrun_until_stoppedis unchanged.User Impact
A stuck env child in once-mode fails after 15s instead of hanging
ocm service --once. Children that exit on their own still report their exit status.Evidence
terminal output from a rustc one-off against
/bin/sleep 30. Naivechild.wait()is still running after 1s. The timed waiter returns an error at the 200ms deadline and the sleep process is gone.The same deadline-and-kill path is what
run_oncenow uses afterspawn_supervisor_child.Real behavior proof
Behavior or issue addressed:
ocm service --onceno longer blocks forever onchild.wait(). A child that does not exit is killed when the 15s deadline is reached, and the supervisor returns an error.Real environment tested: macOS (Darwin 25.6.0 arm64), rustc 1.98.0, ocm checkout
/tmp/ocm-F005onfix/supervisor-run-once-timeoutabove origin/maine04c10166d6b58932213bf8422ccfc201cf6ac37.Exact steps or command run after this patch:
rustc
/tmp/naive_sleep_wait.rsand/tmp/timed_sleep_wait.rs, then run both binaries against/bin/sleep 30. Then run the built lib binaryocm-c8830440f3406352 supervisor::tests::wait_child_with_timeout_kills_sleep_after_deadline --exact --nocapture.Evidence after fix: terminal output above. Naive
child.wait()was still running after 1006ms with sleep still alive. The timed waiter printedsleep timed out after 200ms after 223.674792msandsleep pid 47991 still alive: false.Observed result after fix: The helper returns a timeout error in about 200ms and the sleep child is gone.
run_onceuses that same waiter with a 15s named deadline.What was not tested: A live launchd/systemd
ocm service --oncewith a real OpenClaw gateway that refuses to exit, and the Windows job-object kill path.