Skip to content

Take the block's default exit when a condition's arithmetic fails - #348

Closed
joaothallis wants to merge 1 commit into
developfrom
arithmetic-error-in-condition-takes-default-exit
Closed

joaothallis wants to merge 1 commit into
developfrom
arithmetic-error-in-condition-takes-default-exit

Conversation

@joaothallis

@joaothallis joaothallis commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Why

A condition expression on a Case block can perform arithmetic that faults at runtime even when every operand is a valid number, division by zero being the canonical case. Kernel.//2 raises ArithmeticError, which is neither an Expression.Error nor a RuntimeError, so it slipped past evaluate_expression_block/2's rescue and crashed the process evaluating the flow instead of simply failing the condition.

What

  • evaluate_expression_block/2 now rescues ArithmeticError and returns {:error, :arithmetic, message}. Both condition paths, Case.evaluate_outgoing/5 and Exit.evaluate/2, already treat an {:error, _, _} result as an unusable condition and fall through to the block's default exit, so the run continues. This is exactly how a non-numeric operand (an Expression.Error) already behaves.
  • Reordered Exit.evaluate/2's case clauses. Its {:error, reason, bad_parts} clause was already unreachable, shadowed by the preceding other when not is_boolean(other) guard; the typed error return now makes the dead clause visible to the compiler.

Tests

  • evaluate_expression_block/2 returns {:error, :arithmetic, _} instead of raising.
  • A Case block whose exit test divides by zero selects the default exit.

Scope

Targeted at ArithmeticError (division by zero is the observed high-volume case). Non-numeric operands are already handled via Expression.Error; both now fail the same way, by taking the block's default exit.

🤖 Generated with Claude Code

@joaothallis joaothallis self-assigned this Sep 16, 2026
@joaothallis
joaothallis force-pushed the arithmetic-error-in-condition-takes-default-exit branch from ed86d96 to faa28fd Compare September 17, 2026 00:08
A flow author's Case-block condition can divide by zero (or otherwise
fault) at runtime even when every operand is a valid number. `Kernel.//2`
then raises `ArithmeticError`, which is neither an `Expression.Error` nor a
`RuntimeError`, so it escaped `evaluate_expression_block/2`'s rescue and
crashed the calling process instead of just failing the condition.

Treat a runtime arithmetic fault like any other unusable condition value:
`Exit.evaluate/2` and `Case.evaluate_outgoing/5` already fall through to the
block's default exit for an `{:error, _, _}` result, so the run continues.

Also reorder `Exit.evaluate/2`'s case clauses: the `{:error, reason,
bad_parts}` clause was already unreachable, shadowed by the preceding
`other when not is_boolean(other)` guard. The typed error return now makes the
dead clause visible to the compiler, so move the specific clause first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joaothallis
joaothallis force-pushed the arithmetic-error-in-condition-takes-default-exit branch from faa28fd to f71cd17 Compare September 17, 2026 00:46
@joaothallis

Copy link
Copy Markdown
Contributor Author

Superseded by turnhub/expression#372. The cleaner fix is in the expression library, which already reraises a RuntimeError raised during evaluation as Expression.Error; ArithmeticError was simply a missing case in that clause. With that, evaluate_expression_block/2's existing Expression.Error handling already routes a division-by-zero condition to the default exit, so no flow_runner change is needed.

@joaothallis
joaothallis deleted the arithmetic-error-in-condition-takes-default-exit branch September 17, 2026 01:13
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant