windmill-labs/windmill · error
Cannot parse '{s}' as integer: {e}
Error message
Cannot parse '{s}' as integer: {e} What it means
convert_val converts a JSON string argument into a tokio-postgres ToSql parameter. When the declared arg type is int/integer/int4/serial, it parses the string with i32::from_str and wraps any ParseIntError as 'Cannot parse ... as integer'. It exists because string params coming from JSON must be turned into a valid INT4 client-side.
Source
Thrown at backend/windmill-worker/src/pg_executor.rs:1590
Value::Number(n) if n.is_i64() => Ok((Box::new(n.as_i64().unwrap()), Type::INT8)),
Value::Number(n) => Ok((Box::new(n.as_f64().unwrap()), Type::FLOAT8)),
Value::String(s) if arg_t == "uuid" => Ok((Box::new(Uuid::parse_str(s)?), Type::UUID)),
Value::String(s)
if arg_t == "smallint"
|| arg_t == "smallserial"
|| arg_t == "int2"
|| arg_t == "serial2" =>
{
s.parse::<i16>()
.map(|n| (Box::new(n) as Box<dyn ToSql + Sync + Send>, Type::INT2))
.map_err(|e| anyhow::anyhow!("Cannot parse '{s}' as smallint: {e}").into())
}
Value::String(s)
if arg_t == "int" || arg_t == "integer" || arg_t == "int4" || arg_t == "serial" =>
{
s.parse::<i32>()
.map(|n| (Box::new(n) as Box<dyn ToSql + Sync + Send>, Type::INT4))
.map_err(|e| anyhow::anyhow!("Cannot parse '{s}' as integer: {e}").into())
}
Value::String(s)
if arg_t == "bigint"
|| arg_t == "bigserial"
|| arg_t == "int8"
|| arg_t == "serial8" =>
{
s.parse::<i64>()
.map(|n| (Box::new(n) as Box<dyn ToSql + Sync + Send>, Type::INT8))
.map_err(|e| anyhow::anyhow!("Cannot parse '{s}' as bigint: {e}").into())
}
Value::String(s) if arg_t == "date" => {
let date = parse_naive_date(s)
.map_err(|e| Error::ExecutionErr(format!("Cannot parse '{s}' as date: {e}")))?;
Ok((Box::new(date), Type::DATE))
}
Value::String(s) if arg_t == "time" => {
let time = parse_naive_time(s)View on GitHub (pinned to e474e8803c)
Solutions
- Validate the string is a whole number within i32 range (-2147483648..=2147483647) before invoking the step
- Trim/normalize the input and remove decimal or thousands separators before passing
- If values exceed i32 range, change the arg/column type to bigint/int8
- Pass as a JSON number instead of a string so it follows the numeric conversion path
Example fix
// before
args: { user_id: "42 " } // trailing space -> ParseIntError
// after
args: { user_id: user_id.trim() } // or Number(user_id) validated as integer Defensive patterns
Strategy: validation
Validate before calling
function assertInt4(s) {
const n = Number(typeof s === "string" ? s.trim() : s);
if (!Number.isInteger(n) || n < -2147483648 || n > 2147483647) {
throw new Error(`value ${JSON.stringify(s)} is not a valid integer (i32)`);
}
} Type guard
const isInt4 = (v) => Number.isInteger(Number(typeof v === "string" ? v.trim() : v)) && Number(v) >= -2147483648 && Number(v) <= 2147483647;
Try / catch
try {
await runPgStep(args);
} catch (e) {
if (String(e.message).includes("as integer")) {
throw new Error(`Argument must be an i32 integer, got: ${e.message}`);
}
throw e;
} Prevention
- Trim and normalize numeric strings before passing them
- Route values above 2^31-1 to bigint args instead
- Validate with Number.isInteger before invoking the step
When it happens
Trigger: A step argument typed int/integer/int4/serial receives a Value::String that is not a valid i32 (e.g. '2147483648', '', 'abc', '3.14', or ' 42' with whitespace).
Common situations: Numbers copied from Excel with formatting; values above 2^31-1 that need bigint; decimal inputs; empty or whitespace-only form fields; IDs pasted with trailing characters.
Related errors
- Cannot parse '{s}' as smallint: {e}
- Cannot parse '{s}' as bigint: {e}
- Cannot parse '{s}' as numeric: {e}
- Cannot parse '{s}' as real: {e}
- Cannot parse '{s}' as double: {e}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/2adc3e16b3681fc3.
Report an issue: GitHub.