windmill-labs/windmill · critical

Unable to deserialize group names

Error message

Unable to deserialize group names

What it means

In Windmill's flow execution, an approval/suspend step can require specific user groups. When the requirement is provided as a static input transform, the worker parses its raw JSON string into Vec<String>. If the string is not valid JSON or not an array of strings, serde_json fails and .expect panics the flow job with "Unable to deserialize group names".

Source

Thrown at backend/windmill-worker/src/worker_flow.rs:3465

            // Persist approval user groups conditions, if any. Requires runnning the InputTransform
            let required_events = suspend.required_events.unwrap() as u16;
            let user_auth_required = suspend.user_auth_required.unwrap_or(false);
            let self_approval_disabled = suspend.self_approval_disabled.unwrap_or(false);
            // self_approval_disabled must be persisted even without user_auth_required, otherwise
            // the resume boundary sees no approval_conditions and the restriction is silently
            // dropped. user_groups_required only applies together with user_auth_required.
            if user_auth_required || self_approval_disabled {
                let user_groups_required: Vec<String>;
                if !user_auth_required {
                    user_groups_required = Vec::new();
                } else if let Some(user_groups_required_as_input_transform) =
                    suspend.user_groups_required
                {
                    match user_groups_required_as_input_transform {
                        InputTransform::Static { value } => {
                            user_groups_required = serde_json::from_str::<Vec<String>>(value.get())
                                .expect("Unable to deserialize group names");
                        }
                        InputTransform::Javascript { expr } => {
                            let mut context = HashMap::with_capacity(2);
                            context.insert("result".to_string(), arc_last_job_result.clone());
                            context
                                .insert("previous_result".to_string(), arc_last_job_result.clone());

                            let eval_result = serde_json::from_str::<Vec<String>>(
                                 eval_timeout(
                                     expr.to_string(),
                                     context,
                                     Some(arc_flow_job_args.clone()),
                                     flow_env,
                                     None,
                                     None,
                                     None
                                 )
                                 .warn_after_seconds(3)

View on GitHub (pinned to e474e8803c)

Solutions

  1. Fix the suspend step's user_groups_required static value to be a valid JSON array of strings, e.g. ["admins","devs"]
  2. Re-deploy the corrected flow (UI editor, wmill flow push, or SDK)
  3. If the value must be dynamic, switch the input transform to a JavaScript/AI transform that evaluates to a string array

Example fix

// before (flow definition)
"user_groups_required": { "type": "static", "value": "admins" }
// after
"user_groups_required": { "type": "static", "value": "[\"admins\"]" }
Defensive patterns

Strategy: validation

Validate before calling

let groups: Option<Vec<String>> = serde_json::from_str(value.get()).ok();
if groups.is_none() {
    eprintln!("user_groups_required static value is not a JSON array of strings: {}", value.get());
}

Type guard

fn is_string_array(v: &serde_json::Value) -> bool {
    v.as_array().map_or(false, |a| a.iter().all(|x| x.is_string()))
}

Try / catch

match serde_json::from_str::<Vec<String>>(value.get()) {
    Ok(g) => user_groups_required = g,
    Err(e) => return Err(Error::ExecutionErr(format!("Invalid user_groups_required JSON: {e}"))),
}

Prevention

When it happens

Trigger: A flow suspend step's 'user_groups_required' input transform is set to InputTransform::Static whose value string is not valid JSON (e.g. a bare string, single-quoted JSON, an object, or an array of non-strings).

Common situations: Hand-editing a flow definition YAML/JSON and writing the group list as 'admins, devs' instead of ["admins","devs"]; exporting/importing flows between workspaces with a malformed static value; programmatic flow deploy via SDK with a wrongly-typed value.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/412d6f95aaadee55. Report an issue: GitHub.