0.475 (02 Sep 2026)

2 September, 2026

This release guards division in the SQL that Keel generates from your expressions, and adds a live log view to keel run.

Division guards its divisor

Division in a Keel expression now compiles to SQL that guards its divisor. When the divisor evaluates to zero the division yields null, where previously Postgres raised a division by zero error that aborted the whole statement, taking the surrounding read or write down with it.

The guard sits in the layer that compiles the expression language to SQL, and every expression context shares that layer. So it applies to computed fields, to @permission and @where expressions, to model-level @validate rules, and to @set write inputs alike:

model Order {
  fields {
    total Decimal?
    itemCount Number?
    averageItemPrice Decimal? @computed(order.total / order.itemCount)
  }
}

An order with no items no longer breaks every write to that row. averageItemPrice is null while itemCount is zero, and recomputes to a number as soon as the order has items.

The part worth reading carefully is that the failure mode moves rather than disappearing, and where it moves to depends on the context the division sits in.

A nullable computed field stores the null, which is the case above and the one that behaves the way you would want. A non-nullable computed field cannot store it, so the write is refused by that field's own not-null constraint instead. That is a clearer failure than a division-by-zero error, but it is still a failure: make the field nullable, or guard the expression, if you want the write to succeed.

In a @where or @permission expression the division is part of a predicate, and a predicate that evaluates to null is not true, so the row does not match. A read that used to error now succeeds and returns fewer rows, and a permission that used to error now denies. Nothing complains, so this is a change you will notice in the results rather than in an error.

In a model-level @validate rule the same three-valued logic runs the other way round: only a rule the database can judge to be false counts as a violation, and null is not false. A rule whose divisor is zero therefore does not block the write.

Where you want a zero divisor to produce something other than null, an IF guard is still how you say so:

model Order {
  fields {
    total Decimal
    itemCount Number
    averageItemPrice Decimal @computed(IF(order.itemCount > 0, order.total / order.itemCount, 0))
  }
}

A guard you already wrote for this reason keeps working exactly as before. It is redundant now rather than load-bearing, but there is nothing to undo.

See the Expressions reference for the full details.

A live log view in keel run

While keel run is running, press l to open a log view of what your app is doing. Press l again, or q, or Esc, to return to the normal view. Ctrl-C still stops the server.

Opening the view replays the activity it has already collected, so you land on what just happened rather than a blank screen, and new events then stream in as they arrive. Requests, jobs, subscribers, flow runs and migrations each appear as a timestamped summary line, with that event's attributes indented underneath it:

14:32:07.114  POST /web/json/createPost → 200 (12ms) createPost · Post
              trace                  4bf92f3577b34da6a3ce929d0e0e4736
              api                    web
              protocol               JSON
              action.type            create
              permission             granted
              client.address         ::1
              user_agent.original    curl/8.7.1

Some attributes are hidden by default to keep those blocks readable. Running keel run --verbose reveals them.

The view is deliberately plain scrolling output rather than a full-screen interface, so your terminal's own scrollback, selection and search keep working in it exactly as they do for the rest of the CLI's output.

One change to expect when you upgrade: requests no longer print inline in the normal keel run output. The per-request lines that used to appear in the scroll live in the log view now.

See keel run in the CLI reference for the full details.

Fixes and Improvements

  • JSON API: The generated OpenAPI spec now declares the 409 that a model-level @validate rule returns, for every write action, and tightens the error-response schema so the shape of a data.errors element is described properly rather than leaving it an array of anything. This documents behaviour that already existed rather than changing it: the runtime has returned that 409 since model rules shipped, but the spec never advertised it, so generated clients and contract tests now know about it where before they could not. See JSON schema validation.
  • Flows: A flow run whose flow has been removed from the schema can now be cancelled by whoever started it. Removing a flow from the schema does not cancel its in-flight runs by itself, and the other operations on a removed flow's runs still return not found. See Cancelling a run.

For any issues or feedback, please contact us at help@keel.so.

Thank you for using Keel!