Repository navigation
Hook fleet (5) chaos isolation fixtures - #1336
Conversation
The fleet self-saturates: one SubagentStop burst spawned 15-30 processes that each re-parsed the same transcript, measured at 15.1s p50 wall idle against 1.55s for a single process, with per-hook durations escalating in launch order. dispatch.py now runs opted-in detectors in-process, sequentially, parsing the event once and passing the same parsed payload to every detector. A hook opts in with subagent_stop.dispatch in its manifest, naming a MODULE:FUNCTION entry point (default detect:detect). A detector with no resolvable entry point still runs as a subprocess and its metrics row is marked subprocess_fallback. Each detector keeps its own verdict, its own runs.jsonl row under its own hook name, and its own rule_ids; a detector that raises, exits, or overruns its 4s soft budget is caught and recorded without stopping its siblings. The event budget defaults to 9s for SubagentStop. mirror_stop_hooks_to_subagent_stop.py emits one dispatcher entry for the opted-in hooks and leaves every other hook's direct entry alone. (cherry picked from commit 531f5c8 on plan/hook-fleet-dispatcher-measure-the-herd-then-dispatch-it)
…op hooks dispatch through one process that parses the event once and shares one transcript parse across all opted-in detectors, emitting the same per-detector metrics rows and verdicts as the subprocess fleet. Review lane: behavior Safety invariant: Hook stdout and exit codes stay the same. This slice does not change the other four claims. Files: engine/hooks/_runner/dispatch.py Change types: bugfix Acceptance criteria: The branch contains only this claim, and the review step publishes one pull request. Exit code: 0 Invoker-Finalize-Id: aaa80940-f2d5-4d79-bb40-79c68fe333ad
…e5bede34 — Review claim: SubagentStop hooks dispatch through one process that parses the event once and shares one transcript parse across all opted-in detectors, emitting the same per-detector metrics rows and verdicts as the subprocess fleet. Review lane: behavior Safety invariant: Hook stdout and exit codes stay the same. This slice does not change the other four claims. Files: engine/hooks/_runner/dispatch.py Change types: bugfix Acceptance criteria: The branch contains only this claim, and the review step publishes one pull request.
Subprocess isolation was the hook fleet's free safety property, so the dispatcher has to prove it reproduced that property rather than assume it. _run_sdk_one caught a raising detector but left exit_code at 0, so outcome.classify saw a zero exit with a catstack-hook-error stderr line and recorded caught_error instead of crashed; the except branch now sets exit code 1. Four chaos fixtures under engine/hooks/_runner/tests/chaos (raise, sleep past the soft budget, exit 3, and a sibling that speaks) drive a dispatcher test asserting each failure gets its own row with the right outcome and exit code while both sibling detectors still emit their verdicts and rows. The fixtures are test-only: they live under tests/ and are registered in a scratch manifest, so the mirror script's name allowlist can never install them into a live settings.json. (cherry picked from commit b57f27c on plan/hook-fleet-dispatcher-measure-the-herd-then-dispatch-it)
…tectors that raise, hang past the soft budget, and exit non-zero each produce their own failure row while every sibling detector's verdict and row is still emitted. Review lane: behavior Safety invariant: Hook stdout and exit codes stay the same. This slice does not change the other four claims. Files: engine/hooks/_runner/tests/chaos Change types: bugfix Acceptance criteria: The branch contains only this claim, and the review step publishes one pull request. Exit code: 0 Invoker-Finalize-Id: 7ca2543e-d062-46fa-bbce-58264f6ae48f
…02624463 — Review claim: Fixture detectors that raise, hang past the soft budget, and exit non-zero each produce their own failure row while every sibling detector's verdict and row is still emitted. Review lane: behavior Safety invariant: Hook stdout and exit codes stay the same. This slice does not change the other four claims. Files: engine/hooks/_runner/tests/chaos Change types: bugfix Acceptance criteria: The branch contains only this claim, and the review step publishes one pull request.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: bab474ae-5de3-4110-a837-2ae69bf4e237) |
|
Queued — the merge queue status continues in this comment ↓. |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: bf916f3e-fdb5-45ac-9866-deb0f751d713) |
The SubagentStop dispatcher records an in-process exception as exit 0. This pull request records that same exception as a crashed row, so a sibling can still speak while the failure stays visible. Co-authored-by: Cursor <cursoragent@cursor.com> Change-Id: If063e8ba2ce12336b8a515be81a05bb1885d2ba9
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: 4027ed4e-2bdc-4ae1-9def-e71ea8591f04) |
|
@Mergifyio queue |
Merge Queue Status
This pull request spent 4 hours 31 minutes 37 seconds in the queue, including 1 hour 43 minutes 7 seconds running CI. ReasonThe merge conditions cannot be satisfied: the checks did not pass within the checks timeout of 1 hour These checks were still running:
Failing checks: HintYou may have to fix your CI before adding the pull request to the queue again. If you update this pull request, to fix the CI, it will automatically be requeued once the queue conditions match again. Requeued — the merge queue status continues in this comment ↓. |
|
@Mergifyio queue |
Merge Queue Status
This pull request spent 59 minutes 33 seconds in the queue, including 51 minutes 41 seconds running CI. Required conditions to merge
|
Summary
When one hook check breaks, the others still report, and the broken one is logged as a crash.
Before, a check that threw an error was logged as a handled error. That hid real crashes from the hook health numbers.
Now a thrown error is logged with a failing exit code. A new test runs five checks at once: one throws, one hangs, one quits early, and two behave.
Review Claim
Fixture detectors that raise, hang past the soft budget, and exit non-zero each produce their own failure row, while every sibling detector's verdict and row is still emitted.
Review Lane
behavior
Review Unit
engine-runtime
Safety Invariant
Hook stdout and exit codes stay the same. The dispatcher still exits 0 and still prints sibling verdicts; only the metrics row for a raising detector changes, from
caught_errorwith exit code 0 tocrashedwith exit code 1.Slice Rationale
This is claim 5 of the hook fleet dispatcher work. It needs the in-process dispatcher from #1335, so this branch sits on top of that one and its diff against
mainalso shows that slice until #1335 lands.This slice's own commit is
6d192023: one line in the dispatcher, four fixture detectors, and one test. Keeping it apart lets a reviewer check failure isolation without re-reading the dispatcher.Non-goals
Measured
Command:
cd engine/hooks/_runner && /opt/homebrew/bin/python3.12 -m unittest tests.test_dispatch -k chaos67ed84600b871478Test Plan
Test Plan
cd engine/hooks/_runner && /opt/homebrew/bin/python3.12 -m unittest tests.test_dispatch -v—Ran 21 tests in 10.165s,OK'caught_error' != 'crashed', as shown under MeasuredRun on Python 3.12.14, the version CI uses. The macOS system Python 3.9.6 cannot run the two in-process detector tests: they fail with
ModuleNotFoundError: No module named 'tomllib'.Revert Plan
Revert Plan
git revert 6d192023caught_error.🤖 Generated with Claude Code
Note
Low Risk
One-line metrics classification change for in-process SDK errors; harness stdout/stderr and multi-hook merge behavior are explicitly pinned by tests and described as unchanged.
Overview
In-process SDK hook failures are now recorded as real crashes in metrics instead of
caught_errorwith exit code 0. In_run_sdk_one, non-SystemExitexceptions caught fromdetect()now setexit_codeto 1 (unchanged behavior forSystemExitcodes and timeouts).Adds chaos test fixtures (
raise,hang,SystemExit(3), healthyok) andtest_sdk_chaos_failures_each_emit_rows_without_silencing_siblings, which runs fiveSubagentStopSDK hooks together and asserts each failure gets its own row (crashed/timed_out,dispatch_pathsdk_error) while healthy siblings still spoke and the overall dispatch still exits 0.Reviewed by Cursor Bugbot for commit 92fb3ae. Bugbot is set up for automated code reviews on this repo. Configure here.