windmill-labs/windmill · error

Cannot convert default value to json value: {unparsed} err

Error message

Cannot convert default value to json
	value: {unparsed}
	error: {e}{}

What it means

`find_main_signature` extracts a Ruby parameter's default value byte-range and coerces it to JSON by naively replacing `nil` with `null` and feeding it to `serde_json::from_str`. Ruby default expressions that are not valid JSON (symbols, method calls, unquoted string literals, Ruby hash syntax with symbol keys) fail, producing "Cannot convert default value to json" with the offending value and serde error; if the serde error mentions 'key must be a string', a NOTE about hash syntax is appended.

Source

Thrown at backend/parsers/windmill-parser-ruby/src/lib.rs:143

                        if a.kind() == "identifier" {
                            a.utf8_text(code.as_bytes()).inspect(|name| {
                                args.push(Arg { name: (*name).to_owned(), ..Default::default() })
                            })?;
                        }
                        if a.kind() == "optional_parameter" {
                            let mut walk = a.walk();
                            let mut it = a.children(&mut walk).into_iter();
                            match (it.next().and_then(|n| n.utf8_text(code.as_bytes()).ok()), {
                                // Skip `=`
                                it.next();
                                it.next().map(|n| n.range())
                            }) {
                                (Some(ident), Some(Range { start_byte, end_byte, .. })) => {
                                    let unparsed =
                                        &code[start_byte..end_byte].replace("nil", "null");
                                    let default: Value = serde_json::from_str(unparsed).map_err(
                                        |e|
                                        anyhow!("Cannot convert default value to json\n\tvalue: {unparsed}\n\terror: {e}{}",
                                            if e.to_string().contains("key must be a string") {
                                                "\n\nNOTE: If you are trying to declare default hash, use following syntax:\n { \"<key>\": <value> }"
                                            } else {
                                                ""
                                            }
                                        ))?;
                                    args.push(Arg {
                                        name: ident.to_owned(),
                                        typ: json_to_typ(&default, true),
                                        default: Some(default),
                                        has_default: true,
                                        ..Default::default()
                                    });
                                }
                                _ => {
                                    let Range { start_byte, end_byte, .. } = a.range();
                                    bail!(
                                        "Cannot parse optional parameter: {}",

View on GitHub (pinned to e474e8803c)

Solutions

  1. Rewrite hash defaults in JSON syntax: `{ "key": "value" }` (this is exactly what the appended NOTE suggests)
  2. Use only JSON-compatible literals as defaults: numbers, JSON strings (double-quoted), true/false, null, arrays, objects
  3. Replace symbolic or computed defaults with a simple JSON literal, or drop the default and handle absence in code

Example fix

// before
def main(a, h = { key: "value" })
// after
def main(a, h = { "key": "value" })
Defensive patterns

Strategy: validation

Validate before calling

// # Ruby-side pre-validation of defaults (must be JSON-compatible):
// def main(a, h = { "key": "value" })  # JSON-style hash, symbol-free
// end
// # quick self-check in Ruby: JSON.parse(default.to_json) rescues nothing to fix

Try / catch

match signature_result {
    Ok(m) => m,
    Err(e) if e.to_string().contains("Cannot convert default value to json") => {
        // message already embeds the value and a hash-syntax hint; pass through to user
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Declaring a Ruby script main-signature argument whose default is anything but a JSON literal: `def main(a: :sym)`, `def main(x: "hello"[unquoted variant])`, `def main(h: { key: 1 })` (Ruby symbol keys), `def main(t: Time.now)`, or string defaults written without JSON-compatible quoting.

Common situations: Ruby hash defaults using `{ key: value }` symbol-key syntax instead of JSON `{ "key": value }`; boolean/default casing is fine but symbols, interpolated strings, and expressions are not; users porting Ruby defaults from idiomatic Ruby code.

Related errors


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