windmill-labs/windmill · error

Invalid file resource: Should be a dictionary.

Error message

Invalid file resource: Should be a dictionary.

What it means

parse_file_resource expects each file entry to be a YAML mapping (dictionary). This error is thrown when the parsed Yaml node is anything other than a Yaml::Hash — a string, number, list, or null — so no fields like `target`, `resource` or `variable` can even be read.

Source

Thrown at backend/parsers/windmill-parser-yaml/src/lib.rs:843

        if let Some(Yaml::String(resource_path)) = f.get(&Yaml::String("resource".to_string())) {
            return Ok(FileResource {
                resource_path: ResourceOrVariablePath::Resource(resource_path.clone()),
                target_path,
                mode,
            });
        }
        if let Some(Yaml::String(resource_path)) = f.get(&Yaml::String("variable".to_string())) {
            return Ok(FileResource {
                resource_path: ResourceOrVariablePath::Variable(resource_path.clone()),
                target_path,
                mode,
            });
        }
        return Err(anyhow!(
            "Files should have a `resource` or `variable` field that will be the contents of the local text file"
        ));
    }
    return Err(anyhow!("Invalid file resource: Should be a dictionary."));
}

fn yaml_to_json(yaml: &Yaml) -> serde_json::Value {
    match yaml {
        Yaml::Array(arr) => {
            let json_array: Vec<serde_json::Value> = arr.into_iter().map(yaml_to_json).collect();
            serde_json::Value::Array(json_array)
        }
        Yaml::Hash(hash) => {
            let json_object = hash
                .into_iter()
                .map(|(k, v)| {
                    let key = match k {
                        Yaml::String(s) => s.clone(),
                        _ => k.as_str().unwrap_or("").to_string(),
                    };
                    (key, yaml_to_json(v))
                })

View on GitHub (pinned to e474e8803c)

Solutions

  1. Write each file entry as a mapping with `- target: ...` plus `resource:` or `variable:` keys.
  2. Fix indentation so each entry is a dictionary, not a bare string.
  3. Remove empty (`null`) entries from the files list.
  4. Validate the YAML with a linter (yamllint) before deploying to catch structural flattening.

Example fix

// before
files:
  - /etc/app.conf
// after
files:
  - target: /etc/app.conf
    resource: u/admin/app_config
Defensive patterns

Strategy: type-guard

Validate before calling

fn validate_files_shape(files: &serde_yaml::Value) -> Result<(), String> {
    let list = files.as_sequence().ok_or("files must be a list")?;
    for (i, e) in list.iter().enumerate() {
        if e.as_mapping().is_none() {
            return Err(format!("files[{i}] must be a mapping with target/resource fields"));
        }
    }
    Ok(())
}

Type guard

fn is_file_entry(v: &serde_yaml::Value) -> bool {
    v.as_mapping().map(|m| m.contains_key("target")).unwrap_or(false)
}

Try / catch

match deploy(yaml) {
    Err(e) if e.to_string().contains("Should be a dictionary") => {
        eprintln!("Write file entries as mappings: - target: ... / resource: ...\n{e}");
    }
    other => other,
}

Prevention

When it happens

Trigger: A `files:` section whose entries are bare strings or a list of lists, e.g. `files: [/etc/app.conf]` or `files: - target` written as a plain scalar; also `files:` with a null/empty entry reaching the parser.

Common situations: Converting from a plain list-of-paths format that Windmill doesn't accept; YAML indentation errors that flatten a mapping into a scalar; passing a JSON array of strings instead of array of objects; empty list items left after editing.

Related errors


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