{"record":{"id":"c11bcc99972ac87b","repo":"windmill-labs/windmill","slug":"missing-otyp-for-pg-arg","errorCode":null,"errorMessage":"Missing otyp for pg arg","messagePattern":"Missing otyp for pg arg","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-worker/src/pg_executor.rs","lineNumber":486,"sourceCode":"    let mut default_values: HashMap<i32, serde_json::Value> = HashMap::new();\n\n    for oidx in arg_indices.iter().sorted() {\n        if let Some((arg, value)) = param_idx_to_arg_and_value.get(&oidx) {\n            // Resolve the value: explicit user value > declaration default > NULL.\n            let value: &serde_json::Value = match (value, arg.default.as_ref()) {\n                (Some(v), _) => *v,\n                (None, Some(d)) => default_values.entry(*oidx).or_insert_with(|| d.clone()),\n                (None, None) => {\n                    if !arg.has_default && !missing_args.contains(&arg.name) {\n                        missing_args.push(arg.name.clone());\n                    }\n                    &serde_json::Value::Null\n                }\n            };\n            let arg_t = arg\n                .otyp\n                .as_ref()\n                .ok_or_else(|| anyhow::anyhow!(\"Missing otyp for pg arg\"))?;\n            let typ = &arg.typ;\n            let (param, natural_type) = convert_val(value, arg_t, typ, arg.otyp_inferred)?;\n            query_params.push(param);\n            param_meta.push((arg.name.clone(), json_value_kind(value)));\n            if all_types_resolved {\n                if otyp_to_pg_type(arg_t).is_ok() {\n                    // The Type comes from convert_val (paired with the binding's\n                    // concrete Rust type) rather than from `otyp_to_pg_type(arg_t)`\n                    // — this prevents the parser-default \"text\" otyp from\n                    // forcing an assertion that the encoder can't satisfy\n                    // (e.g. Value::Bool with parser-defaulted text → Type::TEXT\n                    // on a Box<bool>).\n                    param_types.push(natural_type);\n                } else {\n                    all_types_resolved = false;\n                    param_types.clear();\n                }\n            }","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-worker/src/pg_executor.rs#L468-L504","documentation":"The PostgreSQL executor builds typed query parameters from the script's argument definitions; each arg needs an `otyp` (the resolved PostgreSQL OID/type name) to convert the JSON value into a typed parameter. When an argument has no `otyp`, conversion cannot proceed and this error aborts the query preparation.","triggerScenarios":"`do_postgresql_inner` processing args where `arg.otyp` is `None` — the type was never resolved for the parameter (e.g. argument defined without a type in the script UI/resource, or type inference failed and `otyp_inferred` didn't fill it in).","commonSituations":"Script args edited via the API/CLI omitting the type field; a Postgres type not recognized by Windmill so resolution was skipped; older scripts whose stored args lack otyp after an executor change; passing a null/complex JSON value with no declared parameter type.","solutions":["Open the script's argument definition and set an explicit type for the offending argument so `otyp` resolves.","Check the arg name in the error path against the script's args; fix missing/renamed parameters in the code and args list.","If the type is unusual (custom enum, domain), cast it in SQL (`$1::my_type`) or use a supported base type instead.","Re-save the script through the UI so args are re-validated and otyp is populated; retry the job."],"exampleFix":"// before: arg declared without type in script args\n// { \"name\": \"limit\", \"value\": 10 }  // no type -> otyp None\n// after\n// { \"name\": \"limit\", \"value\": 10, \"type\": \"integer\" }","handlingStrategy":"validation","validationCode":"// validate args carry a type before executing the script\nfor arg in args {\n    if arg.otyp.is_none() {\n        return Err(format!(\"argument '{}' has no resolved type; declare it in the script args\", arg.name));\n    }\n}","typeGuard":"fn has_resolved_type(arg: &PgArg) -> bool {\n    arg.otyp.as_ref().map(|t| !t.is_empty()).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Always declare a type for every script argument via the UI or API","Re-save scripts after upgrading Windmill so arg definitions re-resolve","For custom Postgres types, cast in SQL or use a supported base type"],"tags":["postgresql","parameters","type-resolution","sql"],"backgroundTag":"missing-parameter-type","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}