windmill-labs/windmill · error
invalid json-float
Error message
invalid json-float
What it means
pg_cell_to_json_value_with_state converts a Postgres result cell (FLOAT4/FLOAT8 column) into a JSON value. Rust f64 values that are NaN or +/-Infinity have no JSON representation, so serde_json::Number::from_f64 returns None and the code raises 'invalid json-float' instead of silently emitting invalid JSON. It exists to keep job results valid JSON.
Source
Thrown at backend/windmill-worker/src/pg_executor.rs:1812
// JSON has no encoding for NaN / +Inf / -Inf, but Postgres `float4` /
// `float8` (and `numeric`, via the special `'NaN'` value) do return them.
// Pre-fix the worker errored with "invalid json-float", failing the
// entire query. Round-trip these as JSON strings ("NaN", "Infinity",
// "-Infinity") so the rest of the row still comes through; users who
// need numeric semantics can filter them out client-side.
let f64_to_json_number = |raw_val: f64| -> Result<JSONValue, Error> {
if raw_val.is_nan() {
return Ok(JSONValue::String("NaN".to_string()));
}
if raw_val.is_infinite() {
return Ok(JSONValue::String(if raw_val > 0.0 {
"Infinity".to_string()
} else {
"-Infinity".to_string()
}));
}
let temp =
serde_json::Number::from_f64(raw_val).ok_or(anyhow::anyhow!("invalid json-float"))?;
Ok(JSONValue::Number(temp))
};
Ok(match *column.type_() {
// for rust-postgres <> postgres type-mappings: https://docs.rs/postgres/latest/postgres/types/trait.FromSql.html#types
// for postgres types: https://www.postgresql.org/docs/7.4/datatype.html#DATATYPE-TABLE
// single types
Type::BOOL => get_basic(row, column, column_i, |a: bool| Ok(JSONValue::Bool(a)))?,
Type::BIT => get_basic(row, column, column_i, |a: bit_vec::BitVec| match a.len() {
1 => Ok(JSONValue::Bool(a.get(0).unwrap())),
_ => Ok(JSONValue::String(
a.iter()
.map(|x| if x { "1" } else { "0" })
.collect::<String>(),
)),
})?,
Type::INT2 => get_basic(row, column, column_i, |a: i16| {
Ok(JSONValue::Number(serde_json::Number::from(a)))View on GitHub (pinned to e474e8803c)
Solutions
- Coerce non-finite floats to NULL in the query: `CASE WHEN NOT (col::float8 = 'Infinity' OR col::float8 = '-Infinity' OR col::float8 <> col) THEN col END`, or use `CASE WHEN col::text ~ 'Inf|NaN' THEN NULL ELSE col END`
- Prevent NaN/Inf at write time by validating inputs before INSERT
- If sentinels are intentional, cast the column to text in the SELECT so it round-trips as the string 'Infinity' (the nearby code already special-cases some float8 paths this way)
- Fix the producing expression (guard divide-by-zero, use NULLIF) so non-finite values never appear
Example fix
// before SELECT rate FROM metrics; // after SELECT CASE WHEN rate::text ~ '^(NaN|-?Infinity)$' THEN NULL ELSE rate END AS rate FROM metrics;
Defensive patterns
Strategy: fallback
Validate before calling
-- run before converting results, or as the SELECT itself:
-- SELECT CASE WHEN col::text ~ '^(NaN|-?Infinity)$' THEN NULL ELSE col END AS col FROM t
function assertFiniteFloats(rows) {
for (const row of rows) {
for (const [k, v] of Object.entries(row)) {
if (typeof v === "number" && !Number.isFinite(v)) {
throw new Error(`column ${k} contains a non-finite float that cannot be JSON-serialized`);
}
}
}
} Type guard
const isFiniteNumber = (v) => typeof v === "number" && Number.isFinite(v);
Try / catch
try {
const result = await runPgStep(sql);
return result;
} catch (e) {
if (String(e.message).includes("invalid json-float")) {
// retry with non-finite floats coerced to NULL in the query
const safeSql = sql.replace(/\bFROM\b/i, ", 1 FROM"); // or pre-arranged NULL-safe variant
return runPgStep(nullSafeSql);
}
throw e;
} Prevention
- Guard producing expressions with NULLIF to avoid divide-by-zero Infinity
- Coerce NaN/Inf columns to NULL or text in the SELECT
- Validate at write time that float columns never store 'Infinity'/'NaN' sentinels
- When sentinels are intentional, select the column as text so it survives JSON
When it happens
Trigger: A query returns a float column containing NaN, 'Infinity' or '-Infinity' (e.g. INSERT of 'Infinity'::float8 or a computation producing NaN like log(-1), 0/0 via PG float ops, or power(0,-1)) and the row is converted to the step's result JSON.
Common situations: Analytics columns storing PG 'Infinity' sentinels for missing timestamps/rates; divisions by zero on float columns (PG yields Infinity rather than erroring); aggregation results over empty partitions producing NaN.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Could not generate OpenAPI document in JSON format: {}
- Cannot parse '{s}' as real: {e}
- Cannot parse '{s}' as double: {e}
- row over budget
- Completed jobs file must contain an array of jobs
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/65d25e26b3e57728.
Report an issue: GitHub.