Failure scenario
Run a plan with one step whose tool raises or times out and set continue_on_failure: true on that step.
The step is recorded as FAILED/TIMEOUT, but the planner leaves PlanResult.success as True. The CLI therefore prints Status: SUCCESS and exits 0. continue_on_failure should control whether dependent/remaining work proceeds; it should not erase the failed outcome from the plan's final result.
A direct current reproduction with one raising step produces result.success == True alongside result.steps["a"].status == FAILED.
Sites
src/odin/planner.py:146-152 couples final failure accounting to continue_on_failure instead of only coupling dependency flow.
src/odin/cli.py:79-84 reports and exits from the incorrect aggregate success value.
Expected result
Always mark the aggregate plan result unsuccessful when any step fails or times out, while using continue_on_failure solely to decide whether execution may continue and dependents are eligible.
Failure scenario
Run a plan with one step whose tool raises or times out and set
continue_on_failure: trueon that step.The step is recorded as
FAILED/TIMEOUT, but the planner leavesPlanResult.successasTrue. The CLI therefore printsStatus: SUCCESSand exits 0.continue_on_failureshould control whether dependent/remaining work proceeds; it should not erase the failed outcome from the plan's final result.A direct current reproduction with one raising step produces
result.success == Truealongsideresult.steps["a"].status == FAILED.Sites
src/odin/planner.py:146-152couples final failure accounting tocontinue_on_failureinstead of only coupling dependency flow.src/odin/cli.py:79-84reports and exits from the incorrect aggregate success value.Expected result
Always mark the aggregate plan result unsuccessful when any step fails or times out, while using
continue_on_failuresolely to decide whether execution may continue and dependents are eligible.