Repository navigation
Keep command diagnostics from changing breaker categories - #128
Conversation
Add FailureClass::is_exit_status so the breaker no longer copies the "exit_status:" prefix, and note the departure from the reference heuristics in the breaker's module docs. Trim the unit tests to the cases the change adds, run one workflow per integration test instead of five, replace the restart flag with two direct tests, and share a blocked_reason helper across the breaker tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Simplify passCleanup pass for reuse, simplification, efficiency, and fix depth, applied in 3188c94. The behavior is unchanged. Fixed
Left for follow-up
Verification
|
Command diagnostics such as
order.py:502:5,lib.rs:500:13,test_timeout, andbudgetcould make a normal nonzero command exit look transient or budget-exhausted to the circuit breaker. That bypassed deterministic signature counting and could admit a transient-only restart edge.Make
ReferenceClassifiertreat the structuredexit_status:<code>class as deterministic before inspecting message hints. Keep the full failure reason, stdout, andcommand.outputintact. Structured timeout/cancellation handling, fallback infrastructure hints, and hash/signature normalization are unchanged. The Attractor format documentation records this boundary.Refs #126 and fabro-sh/fabro#876.
Reproduction and coverage
Confirmed on main at
50809f9bd2847d20f047c3d5ffc6692fbc2f803d:CommandStepappends output to its reason and setsexit_status:<code>;Stage::into_outcomepreserves both; the host installs the reference circuit breaker, which previously scanned the whole message.Before the fix, the new classifier test classified a line-500 diagnostic as transient. Real shell commands emitting a line-502 diagnostic bypassed the three-repeat breaker and took restart edges until
max_visits=4stopped them. After the fix, repeated failures stop at three, restart edges are blocked on the first failure, and captured diagnostics remain available. These exercise representative compiler/linter output through real commands, without requiring Ruff or Clippy as the failing command.Validation
cargo nextest run --locked -p petri-execution -p petri --lib --test controls, with the pinned host/Docker plugin paths: 45 passed.CARGO_PROFILE_DEV_DEBUG=0 CARGO_INCREMENTAL=0 mise run check: passed. Includes formatting, workspace Clippy with warnings denied, 1,765 tests, 3 doctests, the release build, workflow/whitespace/bundle checks, and the isolated remote-Docker artifact test.LEAKwarning for the existingpetri-driver::resume::resume_reacquires_held_scopestest. A targeted rerun with the same workspace/all-targets/all-features configuration passed without that warning.Boundary and downstream adoption
An exit-status failure now stays deterministic even when its captured output quotes a real-looking network error. Infrastructure failures without that class still use the existing hints; explicit timeouts/cancellation retain priority. This does not change public failure-class vocabulary, retry eligibility, timeout retry allowance, or sandbox architecture.
Fabro main currently locks Petri to
56ec3849ca693974ff778aa5ad4f9f9410d47d29. After this lands, Fabro must update its Petri Git dependency lock to a revision containing the fix, rebuild, and verify its ownfabro-petricommand workflow path: ordinary versus 50x-line diagnostics,test_timeout/budget, three-repeat signature blocking, refusal of transient-only restart edges, preserved output, and timeout/cancellation/infrastructure controls. Leave Fabro #876 open until adoption and that downstream verification are complete; this PR does not modify or validate the Fabro application itself.