windmill-labs/windmill · error
Cannot parse '{s}' as bool: invalid literal
Error message
Cannot parse '{s}' as bool: invalid literal What it means
convert_val converts a JSON string argument into a tokio-postgres ToSql parameter. When the declared arg type is bool/boolean, it accepts exactly the literals Postgres' boolin() accepts (true/t/yes/y/1/on, false/f/no/n/0/off, case-insensitive) and raises 'Cannot parse ... as bool: invalid literal' for anything else. It exists to mirror PG's own boolean input rules client-side.
Source
Thrown at backend/windmill-worker/src/pg_executor.rs:1667
Value::String(s)
if arg_t == "double" || arg_t == "double precision" || arg_t == "float8" =>
{
s.parse::<f64>()
.map(|n| (Box::new(n) as Box<dyn ToSql + Sync + Send>, Type::FLOAT8))
.map_err(|e| anyhow::anyhow!("Cannot parse '{s}' as double: {e}").into())
}
Value::String(s) if arg_t == "oid" => s
.parse::<u32>()
.map(|n| (Box::new(n) as Box<dyn ToSql + Sync + Send>, Type::OID))
.map_err(|e| anyhow::anyhow!("Cannot parse '{s}' as oid: {e}").into()),
Value::String(s) if arg_t == "bool" || arg_t == "boolean" => {
// Accept the same literals Postgres' boolin() does.
let b = match s.to_ascii_lowercase().as_str() {
"true" | "t" | "yes" | "y" | "1" | "on" => true,
"false" | "f" | "no" | "n" | "0" | "off" => false,
_ => {
return Err(
anyhow::anyhow!("Cannot parse '{s}' as bool: invalid literal").into(),
)
}
};
Ok((Box::new(b), Type::BOOL))
}
Value::String(s) if arg_t == "varchar" || arg_t == "character varying" => {
Ok((Box::new(s.clone()), Type::VARCHAR))
}
// For arg_t in (json, jsonb): bind a JSON-encodable Value with the
// matching pg type. Falling through to TEXT here would assert TEXT
// and break query_typed_raw's encoder check.
// Object / Array (no `[]` suffix): bind as JSONB by default and
// JSON-stringify when the target is text-like.
//
// Note the asymmetry vs the Bool/Number arms above: we coerce to
// text on `matches!(typ, Typ::Str(_))` (which is true for both
// explicit `(text)` decls AND parser-default text), not on
// `explicit_text_target`. Reason: serialising a JSON object/arrayView on GitHub (pinned to e474e8803c)
Solutions
- Map application-level truthy/falsy values to one of PG's accepted literals ('true'/'false') before invoking the step
- Pass an actual JSON boolean instead of a string so the boolean arm handles it directly
- Add form validation so the toggle only emits 'true'/'false'
- If empty means unset, convert to null and use a nullable boolean column instead of sending ''
Example fix
// before
args: { active: feature.enabled ? "ENABLED" : "DISABLED" } // invalid literal
// after
args: { active: feature.enabled ? "true" : "false" } Defensive patterns
Strategy: type-guard
Validate before calling
const PG_BOOL_LITERALS = new Set(["true","t","yes","y","1","on","false","f","no","n","0","off"]);
function assertPgBool(v) {
if (typeof v === "boolean") return;
if (typeof v !== "string" || !PG_BOOL_LITERALS.has(v.trim().toLowerCase())) {
throw new Error(`value ${JSON.stringify(v)} is not a valid PG boolean literal`);
}
} Type guard
function isPgBoolLiteral(v) {
return typeof v === "boolean" || (typeof v === "string" && PG_BOOL_LITERALS.has(v.trim().toLowerCase()));
} Try / catch
try {
await runPgStep(args);
} catch (e) {
if (String(e.message).includes("as bool")) {
throw new Error(`Boolean arg must be true/false (or t/yes/y/1/on, f/no/n/0/off), got: ${e.message}`);
}
throw e;
} Prevention
- Send real JSON booleans instead of strings whenever possible
- Map app-level flags (enabled/disabled, checked/unchecked) to true/false before invoking
- Convert empty/unset toggles to null against a nullable column rather than ""
When it happens
Trigger: A step argument typed bool receives a Value::String like 'True ' with trailing whitespace is fine only if lowercased? No — the match lowercases, so failures come from literals outside the accepted set: '0' works, but 'wahr', 'vrai', 'enabled', 'OK', or an empty string all fail.
Common situations: Local-language boolean words; flags from other systems such as 'Y'/'N' are fine but 'ENABLED'/'DISABLED' are not; form checkboxes sending 'checked'/'unchecked'; empty strings from unset toggles.
Related errors
- Cannot parse '{s}' as smallint: {e}
- Cannot parse '{s}' as integer: {e}
- Cannot parse '{s}' as bigint: {e}
- Cannot parse '{s}' as numeric: {e}
- Cannot parse '{s}' as real: {e}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/2b495fcd85ec7a67.
Report an issue: GitHub.